/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hive.common.type; import java.math.BigInteger; import java.nio.IntBuffer; /** * This code was based on code from Microsoft's PolyBase. * * Represents a signed 128-bit integer. This object is much faster and more * compact than BigInteger, but has many limitations explained in * {@link UnsignedInt128}. In short, this class is a thin wrapper for * {@link UnsignedInt128} to make it signed. This object can be used to * represent a few SQL data types, such as DATETIMEOFFSET in SQLServer. */ public final class SignedInt128 extends Number implements Comparable<SignedInt128> { /** Maximum value that can be represented in this class. */ public static final SignedInt128 MAX_VALUE = new SignedInt128(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF); /** Minimum value that can be represented in this class. */ public static final SignedInt128 MIN_VALUE = new SignedInt128(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); /** For Serializable. */ private static final long serialVersionUID = 1L; /** Magnitude. Core implementation of this object. */ private final UnsignedInt128 mag; /** * Whether the value is negative (zero is NOT negative). When serialized, this * flag is combined into the most significant integer in mag. In other words, * this object can use only 127 bits in mag. UnsignedInt128 itself can handle * 128 bits data. */ private boolean negative; /** * Determines the number of ints to store one value. * * @param precision * precision (0-38) * @return the number of ints to store one value */ public static int getIntsPerElement(int precision) { return UnsignedInt128.getIntsPerElement(precision); } /** * Empty constructor to construct zero. */ public SignedInt128() { this.negative = false; this.mag = new UnsignedInt128(0, 0, 0, 0); } /** * Construct this object from a long value. * * @param v * long value */ public SignedInt128(long v) { this.negative = v < 0L; this.mag = new UnsignedInt128(v < 0 ? -v : v); } /** * Construct this object from UnsignedInt128. The highest bit of the * UnsignedInt128 is converted as the sign bit just like conversion between * int/uint in C++. * * @param mag * UnsignedInt128 object */ public SignedInt128(UnsignedInt128 mag) { this.negative = mag.getV3() < 0; this.mag = new UnsignedInt128(mag.getV0(), mag.getV1(), mag.getV2(), mag.getV3() & SqlMathUtil.FULLBITS_31); } /** * Copy Constructor. * * @param o * object to copy from */ public SignedInt128(SignedInt128 o) { this.negative = o.negative; this.mag = new UnsignedInt128(o.mag); } /** * Construct this object from the given integers. The highest bit of v3 is * converted as the sign bit just like conversion between int/uint in C++. * * @param v0 * v0 * @param v1 * v1 * @param v2 * v2 * @param v3 * v3 */ public SignedInt128(int v0, int v1, int v2, int v3) { this.negative = v3 < 0; this.mag = new UnsignedInt128(v0, v1, v2, v3 & SqlMathUtil.FULLBITS_31); } /** * Constructs from the given string. * * @param str * string */ public SignedInt128(String str) { this(); update(str); } /** * Constructs from the given string with given offset and length. * * @param str * string * @param offset * offset * @param length * length */ public SignedInt128(char[] str, int offset, int length) { this(); update(str, offset, length); } /** @return v[0] */ public int getV0() { return this.mag.getV0(); } /** @return v[1] */ public int getV1() { return this.mag.getV1(); } /** @return v[2] */ public int getV2() { return this.mag.getV2(); } /** @return v[3] */ public int getV3() { return this.mag.getV3(); } /** Make the value to zero. */ public void zeroClear() { this.mag.zeroClear(); this.negative = false; } /** * Update this object with the given long value. * * @param v * long value */ public void update(long v) { this.negative = v < 0L; this.mag.update(v < 0 ? -v : v); } /** * Update this object with the value of the given object. * * @param o * object to copy from */ public void update(SignedInt128 o) { this.negative = o.negative; this.mag.update(o.mag); } /** * Updates the value of this object with the given string. * * @param str * string */ public void update(String str) { update(str.toCharArray(), 0, str.length()); } /** * Updates the value of this object from the given string with given offset * and length. * * @param str * string * @param offset * offset * @param length * length */ public void update(char[] str, int offset, int length) { if (length == 0) { this.zeroClear(); return; } this.negative = false; if (str[offset] == '-') { this.negative = true; ++offset; --length; } else if (str[offset] == '+') { ++offset; --length; } this.mag.update(str, offset, length); if (this.mag.isZero()) { this.negative = false; } } /** * Update this object with the given integers, receiving 128 bits data (full * ranges). * * @param v0 * v0 * @param v1 * v1 * @param v2 * v2 * @param v3 * v3 */ public void update128(int v0, int v1, int v2, int v3) { this.negative = (v3 < 0); this.mag.update(v0, v1, v2, v3 & SqlMathUtil.FULLBITS_31); } /** * Update this object with the given integers, receiving only 96 bits data. * * @param v0 * v0 * @param v1 * v1 * @param v2 * v2 */ public void update96(int v0, int v1, int v2) { this.negative = (v2 < 0); this.mag.update(v0, v1, v2 & SqlMathUtil.FULLBITS_31, 0); } /** * Update this object with the given integers, receiving only 64 bits data. * * @param v0 * v0 * @param v1 * v1 */ public void update64(int v0, int v1) { this.negative = (v1 < 0); this.mag.update(v0, v1 & SqlMathUtil.FULLBITS_31, 0, 0); } /** * Update this object with the given integers, receiving only 32 bits data. * * @param v0 * v0 */ public void update32(int v0) { this.negative = (v0 < 0); this.mag.update(v0 & SqlMathUtil.FULLBITS_31, 0, 0, 0); } /** * Updates the value of this object by reading from the given array, receiving * 128 bits data (full ranges). * * @param array * array to read values from * @param offset * offset of the int array */ public void update128(int[] array, int offset) { update128(array[offset], array[offset + 1], array[offset + 2], array[offset + 3]); } /** * Updates the value of this object by reading from the given array, receiving * only 96 bits data. * * @param array * array to read values from * @param offset * offset of the int array */ public void update96(int[] array, int offset) { update96(array[offset], array[offset + 1], array[offset + 2]); } /** * Updates the value of this object by reading from the given array, receiving * only 64 bits data. * * @param array * array to read values from * @param offset * offset of the int array */ public void update64(int[] array, int offset) { update64(array[offset], array[offset + 1]); } /** * Updates the value of this object by reading from the given array, receiving * only 32 bits data. * * @param array * array to read values from * @param offset * offset of the int array */ public void update32(int[] array, int offset) { update32(array[offset]); } /** * Updates the value of this object by reading from ByteBuffer, receiving 128 * bits data (full ranges). * * @param buf * ByteBuffer to read values from */ public void update128(IntBuffer buf) { update128(buf.get(), buf.get(), buf.get(), buf.get()); } /** * Updates the value of this object by reading from ByteBuffer, receiving only * 96 bits data. * * @param buf * ByteBuffer to read values from */ public void update96(IntBuffer buf) { update96(buf.get(), buf.get(), buf.get()); } /** * Updates the value of this object by reading from ByteBuffer, receiving only * 64 bits data. * * @param buf * ByteBuffer to read values from */ public void update64(IntBuffer buf) { update64(buf.get(), buf.get()); } /** * Updates the value of this object by reading from ByteBuffer, receiving only * 32 bits data. * * @param buf * ByteBuffer to read values from */ public void update32(IntBuffer buf) { update32(buf.get()); } /** * Serializes the value of this object to the given array, putting 128 bits * data (full ranges). * * @param array * array to use * @param offset * offset of the int array */ public void serializeTo128(int[] array, int offset) { assert (this.mag.getV3() >= 0); array[offset] = this.mag.getV0(); array[offset + 1] = this.mag.getV1(); array[offset + 2] = this.mag.getV2(); array[offset + 3] = this.mag.getV3() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0); } /** * Serializes the value of this object to the given array, putting only 96 * bits data. * * @param array * array to use * @param offset * offset of the int array */ public void serializeTo96(int[] array, int offset) { assert (this.mag.getV3() == 0 && this.mag.getV2() >= 0); array[offset] = this.mag.getV0(); array[offset + 1] = this.mag.getV1(); array[offset + 2] = this.mag.getV2() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0); } /** * Serializes the value of this object to the given array, putting only 64 * bits data. * * @param array * array to use * @param offset * offset of the int array */ public void serializeTo64(int[] array, int offset) { assert (this.mag.getV3() == 0 && this.mag.getV2() == 0 && this.mag.getV1() >= 0); array[offset] = this.mag.getV0(); array[offset + 1] = this.mag.getV1() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0); } /** * Serializes the value of this object to the given array, putting only 32 * bits data. * * @param array * array to use * @param offset * offset of the int array */ public void serializeTo32(int[] array, int offset) { assert (this.mag.getV3() == 0 && this.mag.getV2() == 0 && this.mag.getV1() == 0 && this.mag.getV0() >= 0); array[offset] = this.mag.getV0() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0); } /** * Serializes the value of this object to ByteBuffer, putting 128 bits data * (full ranges). * * @param buf * ByteBuffer to use */ public void serializeTo128(IntBuffer buf) { assert (this.mag.getV3() >= 0); buf.put(this.mag.getV0()); buf.put(this.mag.getV1()); buf.put(this.mag.getV2()); buf.put(this.mag.getV3() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0)); } /** * Serializes the value of this object to ByteBuffer, putting only 96 bits * data. * * @param buf * ByteBuffer to use */ public void serializeTo96(IntBuffer buf) { assert (this.mag.getV3() == 0 && this.mag.getV2() >= 0); buf.put(this.mag.getV0()); buf.put(this.mag.getV1()); buf.put(this.mag.getV2() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0)); } /** * Serializes the value of this object to ByteBuffer, putting only 64 bits * data. * * @param buf * ByteBuffer to use */ public void serializeTo64(IntBuffer buf) { assert (this.mag.getV3() == 0 && this.mag.getV2() == 0 && this.mag.getV1() >= 0); buf.put(this.mag.getV0()); buf.put(this.mag.getV1() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0)); } /** * Serializes the value of this object to ByteBuffer, putting only 32 bits * data. * * @param buf * ByteBuffer to use */ public void serializeTo32(IntBuffer buf) { assert (this.mag.getV3() == 0 && this.mag.getV2() == 0 && this.mag.getV1() == 0 && this.mag.getV0() >= 0); buf.put(this.mag.getV0() | (this.negative ? SqlMathUtil.NEGATIVE_INT_MASK : 0)); } /** * @return Whether this object represents zero. */ public boolean isZero() { return this.mag.isZero(); } @Override public boolean equals(Object obj) { if (obj instanceof SignedInt128) { SignedInt128 o = (SignedInt128) obj; return this.negative == o.negative && mag.equals(o.mag); } else { return false; } } /** * Specialized version. * * @param o * the object to compare * @return whether the object is equal to this object */ public boolean equals(SignedInt128 o) { return this.negative == o.negative && mag.equals(o.mag); } @Override public int hashCode() { return this.negative ? -mag.hashCode() : mag.hashCode(); } @Override public int compareTo(SignedInt128 o) { if (negative) { if (o.negative) { return o.mag.compareTo(mag); } else { return -1; } } else { if (o.negative) { return 1; } else { return mag.compareTo(o.mag); } } } @Override public int intValue() { int unsigned = this.mag.getV0() & SqlMathUtil.FULLBITS_31; return this.negative ? -unsigned : unsigned; } @Override public long longValue() { long unsigned = SqlMathUtil.combineInts(this.mag.getV0(), this.mag.getV1()) & SqlMathUtil.FULLBITS_63; return this.negative ? -unsigned : unsigned; } @Override public float floatValue() { return intValue(); } @Override public double doubleValue() { return longValue(); } /** * Calculates addition and puts the result into the given object. This method * is static and not destructive (except the result object). * * @param left * left operand * @param right * right operand * @param result * object to receive the calculation result */ public static void add(SignedInt128 left, SignedInt128 right, SignedInt128 result) { result.update(left); result.addDestructive(right); } /** * Calculates addition and stores the result into this object. This method is * destructive. * * @param right * right operand */ public void addDestructive(SignedInt128 right) { if (this.negative == right.negative) { this.mag.addDestructive(right.mag); if (this.mag.getV3() < 0) { SqlMathUtil.throwOverflowException(); } return; } byte signum = UnsignedInt128.difference(this.mag, right.mag, this.mag); this.negative = (signum > 0 ? this.negative : right.negative); } /** * Calculates subtraction and puts the result into the given object. This * method is static and not destructive (except the result object). * * @param left * left operand * @param right * right operand * @param result * object to receive the calculation result */ public static void subtract(SignedInt128 left, SignedInt128 right, SignedInt128 result) { result.update(left); result.subtractDestructive(right); } /** * Calculates subtraction and stores the result into this object. This method * is destructive. * * @param right * right operand */ public void subtractDestructive(SignedInt128 right) { if (this.negative != right.negative) { this.mag.addDestructive(right.mag); if (this.mag.getV3() < 0) { SqlMathUtil.throwOverflowException(); } return; } byte signum = UnsignedInt128.difference(this.mag, right.mag, this.mag); this.negative = (signum > 0 ? this.negative : !this.negative); } /** * Calculates multiplication and puts the result into the given object. This * method is static and not destructive (except the result object). * * @param left * left operand * @param right * right operand * @param result * object to receive the calculation result */ public static void multiply(SignedInt128 left, SignedInt128 right, SignedInt128 result) { if (result == left || result == right) { throw new IllegalArgumentException( "result object cannot be left or right operand"); } result.update(left); result.multiplyDestructive(right); } /** * Performs multiplication. * * @param right * right operand. this object is not modified. */ public void multiplyDestructive(SignedInt128 right) { this.mag.multiplyDestructive(right.mag); this.negative = this.negative ^ right.negative; if (this.mag.getV3() < 0) { SqlMathUtil.throwOverflowException(); } } /** * Performs multiplication. * * @param right * right operand. */ public void multiplyDestructive(int right) { if (right < 0) { this.mag.multiplyDestructive(-right); this.negative = !this.negative; } else { this.mag.multiplyDestructive(right); } if (this.mag.isZero()) { this.negative = false; } if (this.mag.getV3() < 0) { SqlMathUtil.throwOverflowException(); } } /** * Divides this value with the given value. This version is destructive, * meaning it modifies this object. * * @param right * the value to divide * @return remainder */ public int divideDestructive(int right) { int ret; if (right < 0) { ret = this.mag.divideDestructive(-right); this.negative = !this.negative; } else { ret = this.mag.divideDestructive(right); } ret = ret & SqlMathUtil.FULLBITS_31; if (this.negative) { ret = -ret; } if (this.mag.isZero()) { this.negative = false; } return ret; } /** * Performs division and puts the quotient into the given object. This method * is static and not destructive (except the result object). * * @param left * left operand * @param right * right operand * @param quotient * result object to receive the calculation result * @param remainder * result object to receive the calculation result */ public static void divide(SignedInt128 left, SignedInt128 right, SignedInt128 quotient, SignedInt128 remainder) { if (quotient == left || quotient == right) { throw new IllegalArgumentException( "result object cannot be left or right operand"); } quotient.update(left); quotient.divideDestructive(right, remainder); } /** * Performs division and puts the quotient into this object. * * @param right * right operand. this object is not modified. * @param remainder * result object to receive the calculation result */ public void divideDestructive(SignedInt128 right, SignedInt128 remainder) { this.mag.divideDestructive(right.mag, remainder.mag); remainder.negative = false; // remainder is always positive this.negative = this.negative ^ right.negative; } /** * Reverses the sign of this object. This method is destructive. */ public void negateDestructive() { this.negative = !this.negative; } /** * Makes this object positive. This method is destructive. */ public void absDestructive() { this.negative = false; } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result */ public static void negate(SignedInt128 left, SignedInt128 result) { result.update(left); result.negateDestructive(); } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result */ public static void abs(SignedInt128 left, SignedInt128 result) { result.update(left); result.absDestructive(); } /** * Adds one to this value. This version is destructive, meaning it modifies * this object. */ public void incrementDestructive() { if (!this.negative) { if (this.mag.equals(SqlMathUtil.FULLBITS_32, SqlMathUtil.FULLBITS_32, SqlMathUtil.FULLBITS_32, SqlMathUtil.FULLBITS_31)) { SqlMathUtil.throwOverflowException(); } this.mag.incrementDestructive(); assert (this.mag.getV3() >= 0); } else { assert (!this.mag.isZero()); this.mag.decrementDestructive(); if (this.mag.isZero()) { this.negative = false; } } } /** * Subtracts one from this value. This version is destructive, meaning it * modifies this object. */ public void decrementDestructive() { if (this.negative) { if (this.mag.equals(SqlMathUtil.FULLBITS_32, SqlMathUtil.FULLBITS_32, SqlMathUtil.FULLBITS_32, SqlMathUtil.FULLBITS_31)) { SqlMathUtil.throwOverflowException(); } this.mag.incrementDestructive(); assert (this.mag.getV3() >= 0); } else { if (this.mag.isZero()) { this.negative = true; this.mag.incrementDestructive(); } else { this.mag.decrementDestructive(); } } } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result */ public static void increment(SignedInt128 left, SignedInt128 result) { result.update(left); result.incrementDestructive(); } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result */ public static void decrement(SignedInt128 left, SignedInt128 result) { result.update(left); result.decrementDestructive(); } /** * Right-shift for the given number of bits. This bit-shift is equivalent to * Java's signed bit shift ">>". This version is destructive, meaning it * modifies this object. NOTE: So far we don't provide an equivalent of the * unsigned right bit shift ">>>" because we recommend to simply use * {@link UnsignedInt128} for unsigned use. * * @param bits * the number of bits. must be positive * @param roundUp * whether to round up the most significant bit that was discarded */ public void shiftRightDestructive(int bits, boolean roundUp) { this.mag.shiftRightDestructive(bits, roundUp); if (this.mag.isZero() && this.negative) { this.negative = false; } } /** * Left-shift for the given number of bits. This bit-shift is equivalent to * Java's signed bit shift "<<". This method does not throw an error * even if overflow happens. This version is destructive, meaning it modifies * this object. * * @param bits * the number of bits. must be positive */ public void shiftLeftDestructive(int bits) { this.mag.shiftLeftDestructive(bits); if (this.mag.getV3() < 0) { SqlMathUtil.throwOverflowException(); } assert (this.mag.getV3() >= 0); } /** * Scale down the value for 10**tenScale (this := this / 10**tenScale). This * method rounds-up, eg 44/10=4, 45/10=5. This version is destructive, meaning * it modifies this object. * * @param tenScale * scaling. must be positive */ public void scaleDownTenDestructive(short tenScale) { this.mag.scaleDownTenDestructive(tenScale); if (this.mag.isZero() && this.negative) { this.negative = false; } } /** * Scale up the value for 10**tenScale (this := this * 10**tenScale). Scaling * up DOES throw an error when an overflow occurs. For example, 42.scaleUp(1) * = 420, 42.scaleUp(40) = ArithmeticException. This version is destructive, * meaning it modifies this object. * * @param tenScale * scaling. must be positive */ public void scaleUpTenDestructive(short tenScale) { this.mag.scaleUpTenDestructive(tenScale); if (this.mag.getV3() < 0) { SqlMathUtil.throwOverflowException(); } } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result * @param bits * the number of bits. must be positive * @param roundUp * whether to round up the most significant bit that was discarded */ public static void shiftRight(SignedInt128 left, SignedInt128 result, int bits, boolean roundUp) { result.update(left); result.shiftRightDestructive(bits, roundUp); } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result * @param bits * the number of bits. must be positive */ public static void shiftLeft(SignedInt128 left, SignedInt128 result, int bits) { result.update(left); result.shiftLeftDestructive(bits); } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result * @param tenScale * scaling. must be positive */ public static void scaleDownTen(SignedInt128 left, SignedInt128 result, short tenScale) { result.update(left); result.scaleDownTenDestructive(tenScale); } /** * This version returns the result as a new object, not modifying the give * objects. * * @param left * left operand * @param result * object to receive the calculation result * @param tenScale * scaling. must be positive */ public static void scaleUpTen(SignedInt128 left, SignedInt128 result, short tenScale) { result.update(left); result.scaleUpTenDestructive(tenScale); } /** * Convert this object to {@link BigInteger}. Do not use this method in a * performance sensitive place. * * @return BigInteger to represent this object */ public BigInteger toBigIntegerSlow() { BigInteger bigInt = this.mag.toBigIntegerSlow(); return this.negative ? bigInt.negate() : bigInt; } /** * Returns the formal string representation of this value. Unlike the debug * string returned by {@link #toString()}, this method returns a string that * can be used to re-construct this object. Remember, toString() is only for * debugging. * * @return string representation of this value */ public String toFormalString() { if (this.negative) { return "-" + this.mag.toFormalString(); } return this.mag.toFormalString(); } @Override public String toString() { return "SignedInt128 (" + (this.negative ? "negative" : "positive") + "). mag=" + this.mag.toString(); } }