diff --git a/common/src/java/org/apache/hadoop/hive/common/type/Decimal128.java b/common/src/java/org/apache/hadoop/hive/common/type/Decimal128.java new file mode 100644 index 0000000..ec219fd --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/type/Decimal128.java @@ -0,0 +1,1607 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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.BigDecimal; +import java.nio.IntBuffer; + +/** + * This code was originally written for Microsoft PolyBase. + *

+ * A 128-bit fixed-length Decimal value in the ANSI SQL Numeric semantics, + * representing unscaledValue / 10**scale where scale is 0 or positive. + *

+ *

+ * This class is similar to {@link java.math.BigDecimal}, but a few things + * differ to conform to the SQL Numeric semantics. + *

+ *

+ * Scale of this object is specified by the user, not automatically determined + * like {@link java.math.BigDecimal}. This means that underflow is possible + * depending on the scale. {@link java.math.BigDecimal} controls rounding + * behaviors by MathContext, possibly throwing errors. But, underflow is NOT an + * error in ANSI SQL Numeric. "CAST(0.000000000....0001 AS DECIMAL(38,1))" is + * "0.0" without an error. + *

+ *

+ * Because this object is fixed-length, overflow is also possible. Overflow IS + * an error in ANSI SQL Numeric. "CAST(10000 AS DECIMAL(38,38))" throws overflow + * error. + *

+ *

+ * Each arithmetic operator takes scale as a parameter to control its behavior. + * It's user's (or query optimizer's) responsibility to give an appropriate + * scale parameter. + *

+ *

+ * Finally, this class performs MUCH faster than java.math.BigDecimal for a few + * reasons. Its behavior is simple because of the designs above. This class is + * fixed-length without array expansion and re-allocation. This class is + * mutable, allowing reuse of the same object without re-allocation. This class + * and {@link UnsignedInt128} are designed such that minimal heap-object + * allocations are required for most operations. The only exception is division. + * Even this class requires a few object allocations for division, though much + * fewer than BigDecimal. + *

+ */ +public final class Decimal128 extends Number implements Comparable { + /** Maximum value for #scale. */ + public static final short MAX_SCALE = 38; + + /** Minimum value for #scale. */ + public static final short MIN_SCALE = 0; + + /** Maximum value that can be represented in this class. */ + public static final Decimal128 MAX_VALUE = new Decimal128( + UnsignedInt128.TEN_TO_THIRTYEIGHT, (short) 0, false); + + /** Minimum value that can be represented in this class. */ + public static final Decimal128 MIN_VALUE = new Decimal128( + UnsignedInt128.TEN_TO_THIRTYEIGHT, (short) 0, true); + + /** For Serializable. */ + private static final long serialVersionUID = 1L; + + /** + * The unscaled value of this Decimal128, as returned by + * {@link #getUnscaledValue()}. + * + * @serial + * @see #getUnscaledValue() + */ + private final UnsignedInt128 unscaledValue; + + /** + * The scale of this Decimal128, as returned by {@link #getScale()}. Unlike + * java.math.BigDecimal, the scale is always zero or positive. The possible + * value range is 0 to 38. + * + * @serial + * @see #getScale() + */ + private short scale; + + /** + * -1 means negative, 0 means zero, 1 means positive. + * + * @serial + * @see #getSignum() + */ + private byte signum; + + /** + * 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) + 1; // +1 for + // scale/signum + } + + /** Construct a zero. */ + public Decimal128() { + this.unscaledValue = new UnsignedInt128(); + this.scale = 0; + this.signum = 0; + } + + /** + * Copy constructor. + * + * @param o + * object to copy from + */ + public Decimal128(Decimal128 o) { + this.unscaledValue = new UnsignedInt128(o.unscaledValue); + this.scale = o.scale; + this.signum = o.signum; + } + + /** + * Translates a {@code double} into a {@code Decimal128} in the given scaling. + * Note that, unlike java.math.BigDecimal, the scaling is given as the + * parameter, not automatically detected. This is one of the differences + * between ANSI SQL Numeric and Java's BigDecimal. See class comments for more + * details. + *

+ * Unchecked exceptions: ArithmeticException if {@code val} overflows in the + * scaling. NumberFormatException if {@code val} is infinite or NaN. + *

+ * + * @param val + * {@code double} value to be converted to {@code Decimal128}. + * @param scale + * scale of the {@code Decimal128}. + */ + public Decimal128(double val, short scale) { + this(); + update(val, scale); + } + + /** + * Translates a {@code UnsignedInt128} unscaled value, an {@code int} scale, + * and sign flag into a {@code Decimal128} . The value of the + * {@code Decimal128} is (unscaledVal × 10-scale). + * + * @param unscaledVal + * unscaled value of the {@code Decimal128}. + * @param scale + * scale of the {@code Decimal128}. + * @param negative + * whether the value is negative + */ + public Decimal128(UnsignedInt128 unscaledVal, short scale, boolean negative) { + checkScaleRange(scale); + this.unscaledValue = new UnsignedInt128(unscaledVal); + this.scale = scale; + if (unscaledValue.isZero()) { + this.signum = 0; + } else { + this.signum = negative ? (byte) -1 : (byte) 1; + } + this.unscaledValue.throwIfExceedsTenToThirtyEight(); + } + + /** + * Translates a {@code long} into a {@code Decimal128}. The scale of the + * {@code Decimal128} is zero. + * + * @param val + * {@code long} value to be converted to {@code Decimal128}. + */ + public Decimal128(long val) { + this(val, (short) 0); + } + + /** + * Translates a {@code long} into a {@code Decimal128} with the given scaling. + * + * @param val + * {@code long} value to be converted to {@code Decimal128}. + * @param scale + * scale of the {@code Decimal128}. + */ + public Decimal128(long val, short scale) { + this(); + update(val, scale); + } + + /** + * Constructs from the given string. + * + * @param str + * string + * @param scale + * scale of the {@code Decimal128}. + */ + public Decimal128(String str, short scale) { + this(); + update(str, scale); + } + + /** + * Constructs from the given string with given offset and length. + * + * @param str + * string + * @param offset + * offset + * @param length + * length + * @param scale + * scale of the {@code Decimal128}. + */ + public Decimal128(char[] str, int offset, int length, short scale) { + this(); + update(str, offset, length, scale); + } + + /** Reset the value of this object to zero. */ + public void zeroClear() { + this.unscaledValue.zeroClear(); + this.signum = 0; + } + + /** @return whether this value represents zero. */ + public boolean isZero() { + assert ((this.signum == 0 && this.unscaledValue.isZero()) || (this.signum != 0 && !this.unscaledValue + .isZero())); + return this.signum == 0; + } + + /** + * Copy the value of given object. + * + * @param o + * object to copy from + */ + public void update(Decimal128 o) { + this.unscaledValue.update(o.unscaledValue); + this.scale = o.scale; + this.signum = o.signum; + } + + /** + * Update the value of this object with the given {@code long}. The scale of + * the {@code Decimal128} is zero. + * + * @param val + * {@code long} value to be set to {@code Decimal128}. + */ + public void update(long val) { + update(val, (short) 0); + } + + /** + * Update the value of this object with the given {@code long} with the given + * scal. + * + * @param val + * {@code long} value to be set to {@code Decimal128}. + * @param scale + * scale of the {@code Decimal128}. + */ + public void update(long val, short scale) { + this.scale = 0; + if (val < 0L) { + this.unscaledValue.update(-val); + this.signum = -1; + } else if (val == 0L) { + zeroClear(); + } else { + this.unscaledValue.update(val); + this.signum = 1; + } + + if (scale != 0) { + changeScaleDestructive(scale); + } + } + + /** + * Update the value of this object with the given {@code double}. in the given + * scaling. Note that, unlike java.math.BigDecimal, the scaling is given as + * the parameter, not automatically detected. This is one of the differences + * between ANSI SQL Numeric and Java's BigDecimal. See class comments for more + * details. + *

+ * Unchecked exceptions: ArithmeticException if {@code val} overflows in the + * scaling. NumberFormatException if {@code val} is infinite or NaN. + *

+ * + * @param val + * {@code double} value to be converted to {@code Decimal128}. + * @param scale + * scale of the {@code Decimal128}. + */ + public void update(double val, short scale) { + if (Double.isInfinite(val) || Double.isNaN(val)) { + throw new NumberFormatException("Infinite or NaN"); + } + + checkScaleRange(scale); + this.scale = scale; + + // Translate the double into sign, exponent and significand, according + // to the formulae in JLS, Section 20.10.22. + long valBits = Double.doubleToLongBits(val); + byte sign = ((valBits >> 63) == 0 ? (byte) 1 : (byte) -1); + short exponent = (short) ((valBits >> 52) & 0x7ffL); + long significand = (exponent == 0 ? (valBits & ((1L << 52) - 1)) << 1 + : (valBits & ((1L << 52) - 1)) | (1L << 52)); + exponent -= 1075; + + // zero check + if (significand == 0) { + zeroClear(); + return; + } + + this.signum = sign; + + // Normalize + while ((significand & 1) == 0) { // i.e., significand is even + significand >>= 1; + exponent++; + } + + // so far same as java.math.BigDecimal, but the scaling below is + // specific to ANSI SQL Numeric. + + // first, underflow is NOT an error in ANSI SQL Numeric. + // CAST(0.000000000....0001 AS DECIMAL(38,1)) is "0.0" without an error. + + // second, overflow IS an error in ANSI SQL Numeric. + // CAST(10000 AS DECIMAL(38,38)) throws overflow error. + + // val == sign * significand * 2**exponent. + // this == sign * unscaledValue / 10**scale. + // so, to make val==this, we need to scale it up/down such that: + // unscaledValue = significand * 2**exponent * 10**scale + // Notice that we must do the scaling carefully to check overflow and + // preserve precision. + this.unscaledValue.update(significand); + if (exponent >= 0) { + + // both parts are scaling up. easy. Just check overflow. + this.unscaledValue.shiftLeftDestructiveCheckOverflow(exponent); + this.unscaledValue.scaleUpTenDestructive(scale); + } else { + + // 2**exponent part is scaling down while 10**scale is scaling up. + // Now it's tricky. + // unscaledValue = significand * 10**scale / 2**twoScaleDown + short twoScaleDown = (short) -exponent; + if (scale >= twoScaleDown) { + + // make both scaling up as follows + // unscaledValue = significand * 5**(scale) * + // 2**(scale-twoScaleDown) + this.unscaledValue.shiftLeftDestructiveCheckOverflow(scale + - twoScaleDown); + this.unscaledValue.scaleUpFiveDestructive(scale); + } else { + + // Gosh, really both scaling up and down. + // unscaledValue = significand * 5**(scale) / + // 2**(twoScaleDown-scale) + // To check overflow while preserving precision, we need to do a + // real multiplication + this.unscaledValue.multiplyShiftDestructive( + SqlMathUtil.POWER_FIVES_INT128[scale], + (short) (twoScaleDown - scale)); + } + } + } + + /** + * Updates the value of this object by reading from ByteBuffer, using the + * required number of ints for the given precision. + * + * @param buf + * ByteBuffer to read values from + * @param precision + * 0 to 38. Decimal digits. + */ + public void update(IntBuffer buf, int precision) { + int scaleAndSignum = buf.get(); + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update(buf, precision); + assert ((signum == 0) == unscaledValue.isZero()); + } + + /** + * Updates the value of this object by reading from ByteBuffer, receiving + * 128+32 bits data (full ranges). + * + * @param buf + * ByteBuffer to read values from + */ + public void update128(IntBuffer buf) { + int scaleAndSignum = buf.get(); + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update128(buf); + assert ((signum == 0) == unscaledValue.isZero()); + } + + /** + * Updates the value of this object by reading from ByteBuffer, receiving only + * 96+32 bits data. + * + * @param buf + * ByteBuffer to read values from + */ + public void update96(IntBuffer buf) { + int scaleAndSignum = buf.get(); + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update96(buf); + assert ((signum == 0) == unscaledValue.isZero()); + } + + /** + * Updates the value of this object by reading from ByteBuffer, receiving only + * 64+32 bits data. + * + * @param buf + * ByteBuffer to read values from + */ + public void update64(IntBuffer buf) { + int scaleAndSignum = buf.get(); + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update64(buf); + assert ((signum == 0) == unscaledValue.isZero()); + } + + /** + * Updates the value of this object by reading from ByteBuffer, receiving only + * 32+32 bits data. + * + * @param buf + * ByteBuffer to read values from + */ + public void update32(IntBuffer buf) { + int scaleAndSignum = buf.get(); + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update32(buf); + assert ((signum == 0) == unscaledValue.isZero()); + } + + /** + * Updates the value of this object by reading from the given array, using the + * required number of ints for the given precision. + * + * @param array + * array to read values from + * @param offset + * offset of the long array + * @param precision + * 0 to 38. Decimal digits. + */ + public void update(int[] array, int offset, int precision) { + int scaleAndSignum = array[offset]; + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update(array, offset + 1, precision); + } + + /** + * Updates the value of this object by reading from the given integers, + * receiving 128+32 bits of data (full range). + * + * @param array + * array to read from + * @param offset + * offset of the int array + */ + public void update128(int[] array, int offset) { + int scaleAndSignum = array[offset]; + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update128(array, offset + 1); + } + + /** + * Updates the value of this object by reading from the given integers, + * receiving only 96+32 bits data. + * + * @param array + * array to read from + * @param offset + * offset of the int array + */ + public void update96(int[] array, int offset) { + int scaleAndSignum = array[offset]; + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update96(array, offset + 1); + } + + /** + * Updates the value of this object by reading from the given integers, + * receiving only 64+32 bits data. + * + * @param array + * array to read from + * @param offset + * offset of the int array + */ + public void update64(int[] array, int offset) { + int scaleAndSignum = array[offset]; + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update64(array, offset + 1); + } + + /** + * Updates the value of this object by reading from the given integers, + * receiving only 32+32 bits data. + * + * @param array + * array to read from + * @param offset + * offset of the int array + */ + public void update32(int[] array, int offset) { + int scaleAndSignum = array[offset]; + this.scale = (short) (scaleAndSignum >> 16); + this.signum = (byte) (scaleAndSignum & 0xFF); + this.unscaledValue.update32(array, offset + 1); + } + + /** + * Updates the value of this object with the given string. + * + * @param str + * string + * @param scale + * scale of the {@code Decimal128}. + */ + public void update(String str, short scale) { + update(str.toCharArray(), 0, str.length(), scale); + } + + /** + * Updates the value of this object from the given string with given offset + * and length. + * + * @param str + * string + * @param offset + * offset + * @param length + * length + * @param scale + * scale of the {@code Decimal128}. + */ + public void update(char[] str, int offset, int length, short scale) { + final int end = offset + length; + assert (end <= str.length); + int cursor = offset; + + // sign mark + boolean negative = false; + if (str[cursor] == '+') { + ++cursor; + } else if (str[cursor] == '-') { + negative = true; + ++cursor; + } + + // Skip leading zeros and compute number of digits in magnitude + while (cursor < end && str[cursor] == '0') { + ++cursor; + } + + this.scale = scale; + zeroClear(); + if (cursor == end) { + return; + } + + // "1234567" => unscaledValue=1234567, negative=false, + // fractionalDigits=0 + // "-1234567.89" => unscaledValue=123456789, negative=true, + // fractionalDigits=2 + // "12.3E7" => unscaledValue=123, negative=false, fractionalDigits=1, + // exponent=7 + // ".123E-7" => unscaledValue=123, negative=false, fractionalDigits=3, + // exponent=-7 + int accumulated = 0; + int accumulatedCount = 0; + boolean fractional = false; // after "."? + int fractionalDigits = 0; + int exponent = 0; + while (cursor < end) { + if (str[cursor] == '.') { + if (fractional) { + + // two dots?? + throw new NumberFormatException("Invalid string:" + + new String(str, offset, length)); + } + fractional = true; + } else if (str[cursor] >= '0' && str[cursor] <= '9') { + if (accumulatedCount == 9) { + this.unscaledValue.scaleUpTenDestructive((short) accumulatedCount); + this.unscaledValue.addDestructive(accumulated); + accumulated = 0; + accumulatedCount = 0; + } + int digit = str[cursor] - '0'; + accumulated = accumulated * 10 + digit; + ++accumulatedCount; + if (fractional) { + ++fractionalDigits; + } + } else if (str[cursor] == 'e' || str[cursor] == 'E') { + // exponent part + ++cursor; + boolean exponentNagative = false; + if (str[cursor] == '+') { + ++cursor; + } else if (str[cursor] == '-') { + exponentNagative = true; + ++cursor; + } + while (cursor < end) { + if (str[cursor] >= '0' && str[cursor] <= '9') { + int exponentDigit = str[cursor] - '0'; + exponent *= 10; + exponent += exponentDigit; + } + ++cursor; + } + if (exponentNagative) { + exponent = -exponent; + } + } else { + throw new NumberFormatException("Invalid string:" + + new String(str, offset, length)); + } + + ++cursor; + } + + if (accumulatedCount > 0) { + this.unscaledValue.scaleUpTenDestructive((short) accumulatedCount); + this.unscaledValue.addDestructive(accumulated); + } + + int scaleAdjust = scale - fractionalDigits + exponent; + if (scaleAdjust > 0) { + this.unscaledValue.scaleUpTenDestructive((short) scaleAdjust); + } else if (scaleAdjust < 0) { + this.unscaledValue.scaleDownTenDestructive((short) -scaleAdjust); + } + this.signum = (byte) (this.unscaledValue.isZero() ? 0 : (negative ? -1 : 1)); + } + + /** + * Serialize this object to the given array, putting the required number of + * ints for the given precision. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + * @param precision + * 0 to 38. Decimal digits. + */ + public void serializeTo(int[] array, int offset, int precision) { + array[offset] = ((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo(array, offset + 1, precision); + } + + /** + * Serialize this object to the given integers, putting 128+32 bits of data + * (full range). + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo128(int[] array, int offset) { + array[offset] = ((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo128(array, offset + 1); + } + + /** + * Serialize this object to the given integers, putting only 96+32 bits of + * data. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo96(int[] array, int offset) { + array[offset] = ((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo96(array, offset + 1); + } + + /** + * Serialize this object to the given integers, putting only 64+32 bits of + * data. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo64(int[] array, int offset) { + array[offset] = ((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo64(array, offset + 1); + } + + /** + * Serialize this object to the given integers, putting only 32+32 bits of + * data. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo32(int[] array, int offset) { + array[offset] = ((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo32(array, offset + 1); + } + + /** + * Serialize this object to the given ByteBuffer, putting the required number + * of ints for the given precision. + * + * @param buf + * ByteBuffer to write values to + * @param precision + * 0 to 38. Decimal digits. + */ + public void serializeTo(IntBuffer buf, int precision) { + buf.put((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo(buf, precision); + } + + /** + * Serialize this object to the given ByteBuffer, putting 128+32 bits of data + * (full range). + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo128(IntBuffer buf) { + buf.put((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo128(buf); + } + + /** + * Serialize this object to the given ByteBuffer, putting only 96+32 bits of + * data. + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo96(IntBuffer buf) { + buf.put((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo96(buf); + } + + /** + * Serialize this object to the given ByteBuffer, putting only 64+32 bits of + * data. + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo64(IntBuffer buf) { + buf.put((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo64(buf); + } + + /** + * Serialize this object to the given ByteBuffer, putting only 32+32 bits of + * data. + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo32(IntBuffer buf) { + buf.put((scale << 16) | (signum & 0xFF)); + this.unscaledValue.serializeTo32(buf); + } + + /** + * Changes the scaling of this {@code Decimal128}, preserving the represented + * value. This method is destructive. + *

+ * This method is NOT just a setter for #scale. It also adjusts the unscaled + * value to preserve the represented value. + *

+ *

+ * When the given scaling is larger than the current scaling, this method + * shrinks the unscaled value accordingly. It will NOT throw any error even if + * underflow happens. + *

+ *

+ * When the given scaling is smaller than the current scaling, this method + * expands the unscaled value accordingly. It does throw an error if overflow + * happens. + *

+ *

+ * Unchecked exceptions: ArithmeticException a negative value is specified or + * overflow. + *

+ * + * @param scale + * new scale. must be 0 or positive. + */ + public void changeScaleDestructive(short scale) { + if (scale == this.scale) { + return; + } + + checkScaleRange(scale); + short scaleDown = (short) (this.scale - scale); + if (scaleDown > 0) { + this.unscaledValue.scaleDownTenDestructive(scaleDown); + if (this.unscaledValue.isZero()) { + this.signum = 0; + } + } else if (scaleDown < 0) { + this.unscaledValue.scaleUpTenDestructive((short) -scaleDown); + } + this.scale = scale; + + this.unscaledValue.throwIfExceedsTenToThirtyEight(); + } + + /** + * Calculates addition and puts the result into the given object. Both + * operands are first scaled up/down to the specified scale, then this method + * performs the operation. This method is static and not destructive (except + * the result object). + *

+ * Unchecked exceptions: ArithmeticException an invalid scale is specified or + * overflow. + *

+ * + * @param left + * left operand + * @param right + * right operand + * @param scale + * scale of the result. must be 0 or positive. + * @param result + * object to receive the calculation result + */ + public static void add(Decimal128 left, Decimal128 right, Decimal128 result, + short scale) { + result.update(left); + result.addDestructive(right, scale); + } + + /** + * Calculates addition and stores the result into this object. This method is + * destructive. + *

+ * Unchecked exceptions: ArithmeticException an invalid scale is specified or + * overflow. + *

+ * + * @param right + * right operand + * @param scale + * scale of the result. must be 0 or positive. + */ + public void addDestructive(Decimal128 right, short scale) { + this.changeScaleDestructive(scale); + if (right.signum == 0) { + return; + } + if (this.signum == 0) { + this.update(right); + this.changeScaleDestructive(scale); + return; + } + + short rightScaleTen = (short) (scale - right.scale); + if (this.signum == right.signum) { + + // if same sign, just add up the absolute values + this.unscaledValue.addDestructiveScaleTen(right.unscaledValue, + rightScaleTen); + } else { + byte cmp = UnsignedInt128.differenceScaleTen(this.unscaledValue, + right.unscaledValue, this.unscaledValue, rightScaleTen); + if (cmp == 0) { + this.signum = 0; + } else if (cmp < 0) { + + // right's signum wins + this.signum = right.signum; + } + + // if left's signum wins, we don't need to do anything + } + + this.unscaledValue.throwIfExceedsTenToThirtyEight(); + } + + /** + * Calculates subtraction and puts the result into the given object. Both + * operands are first scaled up/down to the specified scale, then this method + * performs the operation. This method is static and not destructive (except + * the result object). + *

+ * Unchecked exceptions: ArithmeticException an invalid scale is specified or + * overflow. + *

+ * + * @param left + * left operand + * @param right + * right operand + * @param result + * object to receive the calculation result + * @param scale + * scale of the result. must be 0 or positive. + */ + public static void subtract(Decimal128 left, Decimal128 right, + Decimal128 result, short scale) { + result.update(left); + result.subtractDestructive(right, scale); + } + + /** + * Calculates subtraction and stores the result into this object. This method + * is destructive. + *

+ * Unchecked exceptions: ArithmeticException an invalid scale is specified or + * overflow. + *

+ * + * @param right + * right operand + * @param scale + * scale of the result. must be 0 or positive. + */ + public void subtractDestructive(Decimal128 right, short scale) { + this.changeScaleDestructive(scale); + if (right.signum == 0) { + return; + } + if (this.signum == 0) { + this.update(right); + this.changeScaleDestructive(scale); + this.negateDestructive(); + return; + } + + short rightScaleTen = (short) (scale - right.scale); + if (this.signum != right.signum) { + + // if different sign, just add up the absolute values + this.unscaledValue.addDestructiveScaleTen(right.unscaledValue, + rightScaleTen); + } else { + byte cmp = UnsignedInt128.differenceScaleTen(this.unscaledValue, + right.unscaledValue, this.unscaledValue, rightScaleTen); + if (cmp == 0) { + this.signum = 0; + } else if (cmp < 0) { + + // right's signum wins (notice the negation, because we are + // subtracting right) + this.signum = (byte) -right.signum; + } + + // if left's signum wins, we don't need to do anything + } + + this.unscaledValue.throwIfExceedsTenToThirtyEight(); + } + + /** + * Calculates multiplication and puts the result into the given object. Both + * operands are first scaled up/down to the specified scale, then this method + * performs the operation. This method is static and not destructive (except + * the result object). + *

+ * Unchecked exceptions: ArithmeticException an invalid scale is specified or + * overflow. + *

+ * + * @param left + * left operand + * @param right + * right operand + * @param result + * object to receive the calculation result + * @param scale + * scale of the result. must be 0 or positive. + */ + public static void multiply(Decimal128 left, Decimal128 right, + Decimal128 result, short scale) { + if (result == left || result == right) { + throw new IllegalArgumentException( + "result object cannot be left or right operand"); + } + + result.update(left); + result.multiplyDestructive(right, scale); + } + + /** + * Performs multiplication, changing the scale of this object to the specified + * value. + * + * @param right + * right operand. this object is not modified. + * @param newScale + * scale of the result. must be 0 or positive. + */ + public void multiplyDestructive(Decimal128 right, short newScale) { + if (this.signum == 0 || right.signum == 0) { + this.zeroClear(); + this.scale = newScale; + return; + } + + // this = this.mag / 10**this.scale + // right = right.mag / 10**right.scale + // this * right = this.mag * right.mag / 10**(this.scale + right.scale) + // so, we need to scale down (this.scale + right.scale - newScale) + short currentTotalScale = (short) (this.scale + right.scale); + short scaleBack = (short) (currentTotalScale - newScale); + + if (scaleBack > 0) { + + // we do the scaling down _during_ multiplication to avoid + // unnecessary overflow. + // note that even this could overflow if newScale is too small. + this.unscaledValue.multiplyScaleDownTenDestructive(right.unscaledValue, + scaleBack); + } else { + + // in this case, we are actually scaling up. + // we don't have to do complicated things because doing scaling-up + // after + // multiplication doesn't affect overflow (it doesn't happen or + // happens anyways). + this.unscaledValue.multiplyDestructive(right.unscaledValue); + this.unscaledValue.scaleUpTenDestructive((short) -scaleBack); + } + + this.scale = newScale; + this.signum = (byte) (this.signum * right.signum); + if (this.unscaledValue.isZero()) { + this.signum = 0; // because of scaling down, this could happen + } + this.unscaledValue.throwIfExceedsTenToThirtyEight(); + } + + /** + * Performs division and puts the result into the given object. Both operands + * are first scaled up/down to the specified scale, then this method performs + * the operation. This method is static and not destructive (except the result + * object). + *

+ * Unchecked exceptions: ArithmeticException an invalid scale is specified or + * overflow. + *

+ * + * @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 + * @param scale + * scale of the result. must be 0 or positive. + */ + public static void divide(Decimal128 left, Decimal128 right, + Decimal128 quotient, Decimal128 remainder, short scale) { + if (quotient == left || quotient == right) { + throw new IllegalArgumentException( + "result object cannot be left or right operand"); + } + + quotient.update(left); + quotient.divideDestructive(right, scale, remainder); + } + + /** + * Performs division, changing the scale of this object to the specified + * value. + *

+ * Unchecked exceptions: ArithmeticException an invalid scale is specified or + * overflow. + *

+ * + * @param right + * right operand. this object is not modified. + * @param newScale + * scale of the result. must be 0 or positive. + * @param remainder + * object to receive remainder + */ + public void divideDestructive(Decimal128 right, short newScale, + Decimal128 remainder) { + if (right.signum == 0) { + SqlMathUtil.throwZeroDivisionException(); + } + if (this.signum == 0) { + this.scale = newScale; + remainder.update(this); + return; + } + + // this = this.mag / 10**this.scale + // right = right.mag / 10**right.scale + // this / right = (this.mag / right.mag) / 10**(this.scale - + // right.scale) + // so, we need to scale down (this.scale - right.scale - newScale) + short scaleBack = (short) (this.scale - right.scale - newScale); + if (scaleBack >= 0) { + + // it's easier then because we simply do division and then scale + // down. + this.unscaledValue.divideDestructive(right.unscaledValue, + remainder.unscaledValue); + this.unscaledValue.scaleDownTenDestructive(scaleBack); + remainder.unscaledValue.scaleDownTenDestructive(scaleBack); + } else { + + // in this case, we have to scale up _BEFORE_ division. otherwise we + // might lose precision. this is costly, but inevitable. + this.unscaledValue.divideScaleUpTenDestructive(right.unscaledValue, + (short) -scaleBack, remainder.unscaledValue); + } + + this.scale = newScale; + this.signum = (byte) (this.unscaledValue.isZero() ? 0 + : (this.signum * right.signum)); + remainder.scale = scale; + remainder.signum = (byte) (remainder.unscaledValue.isZero() ? 0 : 1); // remainder + // is + // always + // positive + + this.unscaledValue.throwIfExceedsTenToThirtyEight(); + } + + /** + * Makes this {@code Decimal128} a positive number. Unlike + * java.math.BigDecimal, this method is destructive. + */ + public void absDestructive() { + if (this.signum < 0) { + this.signum = 1; + } + } + + /** + * Reverses the sign of this {@code Decimal128}. Unlike java.math.BigDecimal, + * this method is destructive. + */ + public void negateDestructive() { + this.signum = (byte) (-this.signum); + } + + /** + * Calculate the square root of this value in double precision. + *

+ * Note that this does NOT perform the calculation in infinite accuracy. + * Although this sounds weird, it's at least how SQLServer does it. The SQL + * below demonstrates that SQLServer actually converts it into FLOAT (Java's + * double). + *

+ * + * create table bb (col1 DECIMAL(38,36), col2 DECIMAL (38,36)); + * insert into bb values(1.00435134913958923485982394892384,1.00345982739817298323423423); + * select sqrt(col1) as cola,sqrt(col2) colb into asdasd from bb; + * sp_columns asdasd; + * + *

+ * This SQL tells that the result is in FLOAT data type. One justification is + * that, because DECIMAL is at most 38 digits, double precision is enough for + * sqrt/pow. + *

+ *

+ * This method does not modify this object. + *

+ *

+ * This method throws exception if this value is negative rather than + * returning NaN. This is the SQL semantics (at least in SQLServer). + *

+ * + * @return square root of this value + */ + public double sqrtAsDouble() { + if (this.signum == 0) { + return 0; + } else if (this.signum < 0) { + throw new ArithmeticException("sqrt will not be a real number"); + } + double val = doubleValue(); + return Math.sqrt(val); + } + + /** + *

+ * Calculate the power of this value in double precision. Note that this does + * NOT perform the calculation in infinite accuracy. Although this sounds + * weird, it's at least how SQLServer does it. The SQL below demonstrates that + * SQLServer actually converts it into FLOAT (Java's double). + *

+ * + * create table bb (col1 DECIMAL(38,36), col2 DECIMAL (38,36)); + * insert into bb values(1.00435134913958923485982394892384,1.00345982739817298323423423); + * select power(col1, col2); + * + *

+ * This SQL returns '1.004366436877081000000000000000000000', which is merely + * a power calculated in double precision ( + * "java.lang.Math.pow(1.00435134913958923485982394892384d, 1.00345982739817298323423423d)" + * returns 1.0043664368770813), and then scaled into DECIMAL. The same thing + * happens even when "n" is a positive integer. One justification is that, + * because DECIMAL is at most 38 digits, double precision is enough for + * sqrt/pow. + *

+ *

+ * This method does not modify this object. + *

+ *

+ * This method throws exception if the calculated value is Infinite. This is + * the SQL semantics (at least in SQLServer). + *

+ * + * @param n + * power to raise this object. Unlike + * {@link java.math.BigDecimal#pow(int)}, this can receive a + * fractional number. Instead, this method calculates the value in + * double precision. + * @return power of this value + */ + public double powAsDouble(double n) { + if (this.signum == 0) { + return 0; + } + double val = doubleValue(); + double result = Math.pow(val, n); + if (Double.isInfinite(result) || Double.isNaN(result)) { + SqlMathUtil.throwOverflowException(); + } + return result; + } + + /** + * Returns the signum of this {@code Decimal128}. + * + * @return -1, 0, or 1 as the value of this {@code Decimal128} is negative, + * zero, or positive. + */ + public byte getSignum() { + return signum; + } + + /** + * Returns the scale of this {@code Decimal128}. The scale is the + * positive number of digits to the right of the decimal point. + * + * @return the scale of this {@code Decimal128}. + */ + public short getScale() { + return scale; + } + + /** + * Returns unscaled value of this {@code Decimal128}. Be careful because + * changes on the returned object will affect the object. It is not + * recommended to modify the object this method returns. + * + * @return the unscaled value of this {@code Decimal128}. + */ + public UnsignedInt128 getUnscaledValue() { + return unscaledValue; + } + + // Comparison Operations + + /** + * Compares this {@code Decimal128} with the specified {@code Decimal128}. Two + * {@code Decimal128} objects that are equal in value but have a different + * scale (like 2.0 and 2.00) are considered equal by this method. This method + * is provided in preference to individual methods for each of the six boolean + * comparison operators ({@literal <}, ==, {@literal >}, {@literal >=}, !=, + * {@literal <=}). The suggested idiom for performing these comparisons is: + * {@code (x.compareTo(y)} <op> {@code 0)}, where + * <op> is one of the six comparison operators. + * + * @param val + * {@code Decimal128} to which this {@code Decimal128} is to be + * compared. + * @return a negative integer, zero, or a positive integer as this + * {@code Decimal128} is numerically less than, equal to, or greater + * than {@code val}. + */ + @Override + public int compareTo(Decimal128 val) { + if (val == this) { + return 0; + } + + if (this.signum != val.signum) { + return this.signum - val.signum; + } + + int cmp; + if (this.scale >= val.scale) { + cmp = this.unscaledValue.compareToScaleTen(val.unscaledValue, + (short) (this.scale - val.scale)); + } else { + cmp = val.unscaledValue.compareToScaleTen(this.unscaledValue, + (short) (val.scale - this.scale)); + } + return cmp * this.signum; + } + + /** + * Compares this {@code Decimal128} with the specified {@code Object} for + * equality. Unlike {@link #compareTo(Decimal128) compareTo}, this method + * considers two {@code Decimal128} objects equal only if they are equal in + * value and scale (thus 2.0 is not equal to 2.00 when compared by this + * method). This somewhat confusing behavior is, however, same as + * java.math.BigDecimal. + * + * @param x + * {@code Object} to which this {@code Decimal128} is to be compared. + * @return {@code true} if and only if the specified {@code Object} is a + * {@code Decimal128} whose value and scale are equal to this + * {@code Decimal128}'s. + * @see #compareTo(java.math.Decimal128) + * @see #hashCode + */ + @Override + public boolean equals(Object x) { + if (x == this) { + return true; + } + if (!(x instanceof Decimal128)) { + return false; + } + + Decimal128 xDec = (Decimal128) x; + if (scale != xDec.scale) { + return false; + } + if (signum != xDec.signum) { + return false; + } + + return unscaledValue.equals(xDec.unscaledValue); + } + + /** + * Returns the hash code for this {@code Decimal128}. Note that two + * {@code Decimal128} objects that are numerically equal but differ in scale + * (like 2.0 and 2.00) will generally not have the same hash code. This + * somewhat confusing behavior is, however, same as java.math.BigDecimal. + * + * @return hash code for this {@code Decimal128}. + * @see #equals(Object) + */ + @Override + public int hashCode() { + if (signum == 0) { + return 0; + } + return signum * (scale * 31 + unscaledValue.hashCode()); + } + + /** + * Converts this {@code Decimal128} to a {@code long}. This conversion is + * analogous to the narrowing primitive conversion from {@code double} + * to {@code short} as defined in section 5.1.3 of The Java™ + * Language Specification: any fractional part of this + * {@code Decimal128} will be discarded, and if the resulting " + * {@code UnsignedInt128}" is too big to fit in a {@code long}, only the + * low-order 64 bits are returned. Note that this conversion can lose + * information about the overall magnitude and precision of this + * {@code Decimal128} value. + * + * @return this {@code Decimal128} converted to a {@code long}. + */ + @Override + public long longValue() { + if (signum == 0) { + return 0L; + } + + long ret; + if (scale == 0) { + ret = (this.unscaledValue.getV1()) << 32L | this.unscaledValue.getV0(); + } else { + UnsignedInt128 tmp = new UnsignedInt128(this.unscaledValue); + tmp.scaleDownTenDestructive(scale); + ret = (tmp.getV1()) << 32L | tmp.getV0(); + } + + return SqlMathUtil.setSignBitLong(ret, signum > 0); + } + + /** + * Converts this {@code Decimal128} to an {@code int}. This conversion is + * analogous to the narrowing primitive conversion from {@code double} + * to {@code short} as defined in section 5.1.3 of The Java™ + * Language Specification: any fractional part of this + * {@code Decimal128} will be discarded, and if the resulting " + * {@code UnsignedInt128}" is too big to fit in an {@code int}, only the + * low-order 32 bits are returned. Note that this conversion can lose + * information about the overall magnitude and precision of this + * {@code Decimal128} value. + * + * @return this {@code Decimal128} converted to an {@code int}. + */ + @Override + public int intValue() { + if (signum == 0) { + return 0; + } + + int ret; + if (scale == 0) { + ret = this.unscaledValue.getV0(); + } else { + UnsignedInt128 tmp = new UnsignedInt128(this.unscaledValue); + tmp.scaleDownTenDestructive(scale); + ret = tmp.getV0(); + } + + return SqlMathUtil.setSignBitInt(ret, signum > 0); + } + + /** + * Converts this {@code Decimal128} to a {@code float}. This conversion is + * similar to the narrowing primitive conversion from {@code double} to + * {@code float} as defined in section 5.1.3 of The Java™ Language + * Specification: if this {@code Decimal128} has too great a magnitude + * to represent as a {@code float}, it will be converted to + * {@link Float#NEGATIVE_INFINITY} or {@link Float#POSITIVE_INFINITY} as + * appropriate. Note that even when the return value is finite, this + * conversion can lose information about the precision of the + * {@code Decimal128} value. + * + * @return this {@code Decimal128} converted to a {@code float}. + */ + @Override + public float floatValue() { + + // if this function is frequently used, we need to optimize this. + return Float.parseFloat(toFormalString()); + } + + /** + * Converts this {@code Decimal128} to a {@code double}. This conversion is + * similar to the narrowing primitive conversion from {@code double} to + * {@code float} as defined in section 5.1.3 of The Java™ Language + * Specification: if this {@code Decimal128} has too great a magnitude + * represent as a {@code double}, it will be converted to + * {@link Double#NEGATIVE_INFINITY} or {@link Double#POSITIVE_INFINITY} as + * appropriate. Note that even when the return value is finite, this + * conversion can lose information about the precision of the + * {@code Decimal128} value. + * + * @return this {@code Decimal128} converted to a {@code double}. + */ + @Override + public double doubleValue() { + + // if this function is frequently used, we need to optimize this. + return Double.parseDouble(toFormalString()); + } + + /** + * Converts this object to {@link BigDecimal}. This method is not supposed to + * be used in a performance-sensitive place. + * + * @return {@link BigDecimal} object equivalent to this object. + */ + public BigDecimal toBigDecimal() { + + // if this function is frequently used, we need to optimize this. + return new BigDecimal(toFormalString()); + } + + /** + * Throws an exception if the value of this object exceeds the maximum value + * for the given precision. Remember that underflow is not an error, but + * overflow is an immediate error. + * + * @param precision + * maximum precision + */ + public void checkPrecisionOverflow(int precision) { + if (precision <= 0 || precision > 38) { + throw new IllegalArgumentException("Invalid precision " + precision); + } + + if (this.unscaledValue.compareTo(SqlMathUtil.POWER_TENS_INT128[precision]) >= 0) { + SqlMathUtil.throwOverflowException(); + } + } + + /** + * Throws an exception if the given scale is invalid for this object. + * + * @param scale + * scale value + */ + private static void checkScaleRange(short scale) { + if (scale < MIN_SCALE) { + throw new ArithmeticException( + "Decimal128 does not support negative scaling"); + } + if (scale > MAX_SCALE) { + throw new ArithmeticException("Beyond possible Decimal128 scaling"); + } + } + + /** + * 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.signum == 0) { + return "0"; + } + + StringBuilder buf = new StringBuilder(50); + if (this.signum < 0) { + buf.append('-'); + } + + String unscaled = this.unscaledValue.toFormalString(); + if (unscaled.length() > this.scale) { + + // write out integer part first + // then write out fractional part + buf.append(unscaled, 0, unscaled.length() - this.scale); + + if (this.scale > 0) { + buf.append('.'); + buf.append(unscaled, unscaled.length() - this.scale, unscaled.length()); + } + } else { + + // no integer part + buf.append('0'); + + if (this.scale > 0) { + + // fractional part has, starting with zeros + buf.append('.'); + for (int i = unscaled.length(); i < this.scale; ++i) { + buf.append('0'); + } + buf.append(unscaled); + } + } + + return new String(buf); + } + + @Override + public String toString() { + return toFormalString() + "(Decimal128: scale=" + scale + ", signum=" + + signum + ", BigDecimal.toString=" + toBigDecimal().toString() + + ", unscaledValue=[" + unscaledValue.toString() + "])"; + } +} diff --git a/common/src/java/org/apache/hadoop/hive/common/type/SignedInt128.java b/common/src/java/org/apache/hadoop/hive/common/type/SignedInt128.java new file mode 100644 index 0000000..f784290 --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/type/SignedInt128.java @@ -0,0 +1,1071 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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 originally written for Microsoft 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 { + + /** 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(); + } +} diff --git a/common/src/java/org/apache/hadoop/hive/common/type/SqlMathUtil.java b/common/src/java/org/apache/hadoop/hive/common/type/SqlMathUtil.java new file mode 100644 index 0000000..09af28a --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/type/SqlMathUtil.java @@ -0,0 +1,607 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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.util.Arrays; + +/** + * This code was originally written for Microsoft PolyBase. + * + * Misc utilities used in this package. + */ +public final class SqlMathUtil { + + /** Mask to convert a long to a negative long. */ + public static final long NEGATIVE_LONG_MASK = 0x8000000000000000L; + + /** Mask to convert a long to an unsigned long. */ + public static final long FULLBITS_63 = 0x7FFFFFFFFFFFFFFFL; + + /** Mask to convert an int to a negative int. */ + public static final int NEGATIVE_INT_MASK = 0x80000000; + + /** Mask to convert signed integer to unsigned long. */ + public static final long LONG_MASK = 0xFFFFFFFFL; + + /** Mask to convert an int to an unsigned int. */ + public static final int FULLBITS_31 = 0x7FFFFFFF; + + /** Max unsigned integer. */ + public static final int FULLBITS_32 = 0xFFFFFFFF; + + /** 5^13 fits in 2^31. */ + public static final int MAX_POWER_FIVE_INT31 = 13; + + /** 5^x. All unsigned values. */ + public static final int[] POWER_FIVES_INT31 = new int[MAX_POWER_FIVE_INT31 + 1]; + + /** 5^27 fits in 2^63. */ + public static final int MAX_POWER_FIVE_INT63 = 27; + + /** 5^x. All unsigned values. */ + public static final long[] POWER_FIVES_INT63 = new long[MAX_POWER_FIVE_INT63 + 1]; + + /** 5^55 fits in 2^128. */ + public static final int MAX_POWER_FIVE_INT128 = 55; + + /** 5^x. */ + public static final UnsignedInt128[] POWER_FIVES_INT128 = new UnsignedInt128[MAX_POWER_FIVE_INT128 + 1]; + + /** + * 1/5^x, scaled to 128bits (in other words, 2^128/5^x). Because of flooring, + * this is same or smaller than real value. + */ + public static final UnsignedInt128[] INVERSE_POWER_FIVES_INT128 = new UnsignedInt128[MAX_POWER_FIVE_INT128 + 1]; + + /** 10^9 fits in 2^31. */ + public static final int MAX_POWER_TEN_INT31 = 9; + + /** 10^x. All unsigned values. */ + public static final int[] POWER_TENS_INT31 = new int[MAX_POWER_TEN_INT31 + 1]; + + /** 5 * 10^(x-1). */ + public static final int[] ROUND_POWER_TENS_INT31 = new int[MAX_POWER_TEN_INT31 + 1]; + + /** 10^38 fits in UnsignedInt128. */ + public static final int MAX_POWER_TEN_INT128 = 38; + + /** 10^x. */ + public static final UnsignedInt128[] POWER_TENS_INT128 = new UnsignedInt128[MAX_POWER_TEN_INT128 + 1]; + + /** 5 * 10^(x-1). */ + public static final UnsignedInt128[] ROUND_POWER_TENS_INT128 = new UnsignedInt128[MAX_POWER_TEN_INT128 + 1]; + + /** + * 1/10^x, scaled to 128bits, also word-shifted for better accuracy. Because + * of flooring, this is same or smaller than real value. + */ + public static final UnsignedInt128[] INVERSE_POWER_TENS_INT128 = new UnsignedInt128[MAX_POWER_TEN_INT128 + 1]; + + /** number of words shifted up in each INVERSE_POWER_TENS_INT128. */ + public static final int[] INVERSE_POWER_TENS_INT128_WORD_SHIFTS = new int[MAX_POWER_TEN_INT128 + 1]; + + /** To quickly calculate bit length for up to 256. */ + private static final byte[] BIT_LENGTH; + + /** Used in division. */ + private static final long BASE = (1L << 32); + + /** + * Turn on or off the highest bit of an int value. + * + * @param val + * the value to modify + * @param positive + * whether to turn off (positive) or on (negative). + * @return unsigned int value + */ + public static int setSignBitInt(int val, boolean positive) { + if (positive) { + return val & FULLBITS_31; + } + return val | NEGATIVE_INT_MASK; + } + + /** + * Turn on or off the highest bit of a long value. + * + * @param val + * the value to modify + * @param positive + * whether to turn off (positive) or on (negative). + * @return unsigned long value + */ + public static long setSignBitLong(long val, boolean positive) { + if (positive) { + return val & FULLBITS_63; + } + return val | NEGATIVE_LONG_MASK; + } + + /** + * Returns the minimal number of bits to represent the given integer value. + * + * @param word + * int32 value + * @return the minimal number of bits to represent the given integer value + */ + public static short bitLengthInWord(int word) { + if (word < 0) { + return 32; + } + if (word < (1 << 16)) { + if (word < 1 << 8) { + return BIT_LENGTH[word]; + } else { + return (short) (BIT_LENGTH[word >>> 8] + 8); + } + } else { + if (word < (1 << 24)) { + return (short) (BIT_LENGTH[word >>> 16] + 16); + } else { + return (short) (BIT_LENGTH[word >>> 24] + 24); + } + } + } + + /** + * Returns the minimal number of bits to represent the words. + * + * @param v0 + * v0 + * @param v1 + * v1 + * @param v2 + * v2 + * @param v3 + * v3 + * @return the minimal number of bits to represent the words + */ + public static short bitLength(int v0, int v1, int v2, int v3) { + if (v3 != 0) { + return (short) (bitLengthInWord(v3) + 96); + } + if (v2 != 0) { + return (short) (bitLengthInWord(v2) + 64); + } + if (v1 != 0) { + return (short) (bitLengthInWord(v1) + 32); + } + return bitLengthInWord(v0); + } + + /** + * If we can assume JDK 1.8, this should use + * java.lang.Integer.compareUnsigned(), which will be replaced with intrinsics + * in JVM. + * + * @param x + * the first {@code int} to compare + * @param y + * the second {@code int} to compare + * @return the value {@code 0} if {@code x == y}; a value less than {@code 0} + * if {@code x < y} as unsigned values; and a value greater than + * {@code 0} if {@code x > y} as unsigned values + * @see "http://hg.openjdk.java.net/jdk8/tl/jdk/rev/71200c517524" + */ + public static int compareUnsignedInt(int x, int y) { + + // Can't assume JDK 1.8, so implementing this explicitly. + // return Integer.compare(x + Integer.MIN_VALUE, y + Integer.MIN_VALUE); + if (x == y) { + return 0; + } + if (x + Integer.MIN_VALUE < y + Integer.MIN_VALUE) { + return -1; + } else { + return 1; + } + } + + /** + * If we can assume JDK 1.8, this should use java.lang.Long.compareUnsigned(), + * which will be replaced with intrinsics in JVM. + * + * @param x + * the first {@code int} to compare + * @param y + * the second {@code int} to compare + * @return the value {@code 0} if {@code x == y}; a value less than {@code 0} + * if {@code x < y} as unsigned values; and a value greater than + * {@code 0} if {@code x > y} as unsigned values + * @see "http://hg.openjdk.java.net/jdk8/tl/jdk/rev/71200c517524" + */ + public static int compareUnsignedLong(long x, long y) { + + // Can't assume JDK 1.8, so implementing this explicitly. + // return Long.compare(x + Long.MIN_VALUE, y + Long.MIN_VALUE); + if (x == y) { + return 0; + } + if (x + Long.MIN_VALUE < y + Long.MIN_VALUE) { + return -1; + } else { + return 1; + } + } + + /** + * If we can assume JDK 1.8, this should use java.lang.Long.divideUnsigned(), + * which will be replaced with intrinsics in JVM. + * + * @param dividend + * the value to be divided + * @param divisor + * the value doing the dividing + * @return the unsigned quotient of the first argument divided by the second + * argument + * @see "http://hg.openjdk.java.net/jdk8/tl/jdk/rev/71200c517524" + */ + public static long divideUnsignedLong(long dividend, long divisor) { + if (divisor < 0L) { + + // Answer must be 0 or 1 depending on relative magnitude + // of dividend and divisor. + return (compareUnsignedLong(dividend, divisor)) < 0 ? 0L : 1L; + } + + if (dividend >= 0) { // Both inputs non-negative + return dividend / divisor; + } else { + + // simple division. + // Yes, we should do something like this: + // http://www.hackersdelight.org/divcMore.pdf + // but later... (anyway this will be eventually replaced by + // intrinsics in Java 8) + + // an equivalent algorithm exists in + // com.google.common.primitives.UnsingedLongs + long quotient = ((dividend >>> 1L) / divisor) << 1L; + long remainder = dividend - quotient * divisor; + if (compareUnsignedLong(remainder, divisor) >= 0) { + return quotient + 1; + } + return quotient; + } + } + + /** + * If we can assume JDK 1.8, this should use + * java.lang.Long.remainderUnsigned(), which will be replaced with intrinsics + * in JVM. + * + * @param dividend + * the value to be divided + * @param divisor + * the value doing the dividing + * @return the unsigned remainder of the first argument divided by the second + * argument + * @see "http://hg.openjdk.java.net/jdk8/tl/jdk/rev/71200c517524" + */ + public static long remainderUnsignedLong(long dividend, long divisor) { + if (divisor < 0L) { + + // because divisor is negative, quotient is at most 1. + // remainder must be dividend itself (quotient=0), or dividend - + // divisor + return (compareUnsignedLong(dividend, divisor)) < 0 ? dividend : dividend + - divisor; + } + + if (dividend >= 0L) { // signed comparisons + return dividend % divisor; + } else { + // same above + long quotient = ((dividend >>> 1L) / divisor) << 1L; + long remainder = dividend - quotient * divisor; + if (compareUnsignedLong(remainder, divisor) >= 0) { + return remainder - divisor; + } + return remainder; + } + } + + /** + * @param lo + * low 32bit + * @param hi + * high 32bit + * @return long value that combines the two integers + */ + public static long combineInts(int lo, int hi) { + return ((hi & LONG_MASK) << 32L) | (lo & LONG_MASK); + } + + /** + * @param val + * long value + * @return high 32bit of the given value + */ + public static int extractHiInt(long val) { + return (int) (val >> 32); + } + + /** + * @param val + * long value + * @return low 32bit of the given value + */ + public static int extractLowInt(long val) { + return (int) val; + } + + /** Throws an overflow exception. */ + static void throwOverflowException() { + throw new ArithmeticException("Overflow"); + } + + /** Throws a divide-by-zero exception. */ + static void throwZeroDivisionException() { + throw new ArithmeticException("Divide by zero"); + } + + /** + * Multi-precision one super-digit multiply in place. + * + * @param inOut + * @param multiplier + */ + private static void multiplyMultiPrecision(int[] inOut, int multiplier) { + long multiplierUnsigned = multiplier & SqlMathUtil.LONG_MASK; + long product = 0L; + for (int i = 0; i < inOut.length; ++i) { + product = (inOut[i] & SqlMathUtil.LONG_MASK) * multiplierUnsigned + + (product >>> 32); + inOut[i] = (int) product; + } + if ((product >> 32) != 0) { + SqlMathUtil.throwOverflowException(); + } + } + + /** + * Multi-precision one super-digit divide in place. + * + * @param inOut + * @param divisor + * @return + */ + private static int divideMultiPrecision(int[] inOut, int divisor) { + long divisorUnsigned = divisor & SqlMathUtil.LONG_MASK; + long quotient; + long remainder = 0; + for (int i = inOut.length - 1; i >= 0; --i) { + remainder = (inOut[i] & SqlMathUtil.LONG_MASK) + (remainder << 32); + quotient = remainder / divisorUnsigned; + inOut[i] = (int) quotient; + remainder %= divisorUnsigned; + } + return (int) remainder; + } + + private static int arrayValidLength(int[] array) { + int len = array.length; + while (len >= 0 && array[len - 1] == 0) { + --len; + } + return len < 0 ? 0 : len; + } + + /** + * Multi-precision divide. dividend and divisor not changed. Assumes that + * there is enough room in quotient for results. Drawbacks of this + * implementation: 1) Need one extra super-digit in R 2) As it modifies D + * during work, then it restores it back (this is necessary because the caller + * doesn't expect D to change) 3) Always get Q and R - if R is unnecessary, + * can be slightly faster. + * + * @param dividend + * dividend. in. + * @param divisor + * divisor. in. + * @param quotient + * quotient. out. + * @return remainder + */ + public static int[] divideMultiPrecision(int[] dividend, int[] divisor, + int[] quotient) { + final int dividendLength = arrayValidLength(dividend); + final int divisorLength = arrayValidLength(divisor); + Arrays.fill(quotient, 0); + + // Remainder := Dividend + int[] remainder = new int[dividend.length + 1]; + System.arraycopy(dividend, 0, remainder, 0, dividend.length); + remainder[remainder.length - 1] = 0; + + if (divisorLength == 0) { + throwZeroDivisionException(); + } + if (dividendLength < divisorLength) { + return remainder; + } + if (divisorLength == 1) { + int rem = divideMultiPrecision(remainder, divisor[0]); + System.arraycopy(remainder, 0, quotient, 0, quotient.length); + Arrays.fill(remainder, 0); + remainder[0] = rem; + return remainder; + } + + // Knuth, "The Art of Computer Programming", 3rd edition, vol.II, Alg.D, + // pg 272 + // D1. Normalize so high digit of D >= BASE/2 - that guarantee + // that QH will not be too far from the correct digit later in D3 + int d1 = (int) (BASE / ((divisor[divisorLength - 1] & LONG_MASK) + 1L)); + if (d1 > 1) { + + // We are modifying divisor here, so make a local copy. + int[] newDivisor = new int[divisorLength]; + System.arraycopy(divisor, 0, newDivisor, 0, divisorLength); + multiplyMultiPrecision(newDivisor, d1); + divisor = newDivisor; + multiplyMultiPrecision(remainder, d1); + } + + // only 32bits, but long to behave as unsigned + long dHigh = (divisor[divisorLength - 1] & LONG_MASK); + long dLow = (divisor[divisorLength - 2] & LONG_MASK); + + // D2 already done - iulRindex initialized before normalization of R. + // D3-D7. Loop on iulRindex - obtaining digits one-by-one, as "in paper" + for (int rIndex = remainder.length - 1; rIndex >= divisorLength; --rIndex) { + + // D3. Calculate Q hat - estimation of the next digit + long accum = combineInts(remainder[rIndex - 1], remainder[rIndex]); + int qhat; + if (dHigh == (remainder[rIndex] & LONG_MASK)) { + qhat = (int) (BASE - 1); + } else { + qhat = (int) divideUnsignedLong(accum, dHigh); + } + + int rhat = (int) (accum - (qhat & LONG_MASK) * dHigh); + while (compareUnsignedLong(dLow * (qhat & LONG_MASK), + combineInts(remainder[rIndex - 2], rhat)) > 0) { + qhat--; + if ((rhat & LONG_MASK) >= -((int) dHigh)) { + break; + } + rhat += dHigh; + } + + // D4. Multiply and subtract: (some digits of) R -= D * QH + long dwlMulAccum = 0; + accum = BASE; + int iulRwork = rIndex - divisorLength; + for (int dIndex = 0; dIndex < divisorLength; dIndex++, iulRwork++) { + dwlMulAccum += (qhat & LONG_MASK) * (divisor[dIndex] & LONG_MASK); + accum += (remainder[iulRwork] & LONG_MASK) + - (extractLowInt(dwlMulAccum) & LONG_MASK); + dwlMulAccum = (extractHiInt(dwlMulAccum) & LONG_MASK); + remainder[iulRwork] = extractLowInt(accum); + accum = (extractHiInt(accum) & LONG_MASK) + BASE - 1; + } + accum += (remainder[iulRwork] & LONG_MASK) - dwlMulAccum; + remainder[iulRwork] = extractLowInt(accum); + quotient[rIndex - divisorLength] = qhat; + + // D5. Test remainder. Carry indicates result<0, therefore QH 1 too + // large + if (extractHiInt(accum) == 0) { + + // D6. Add back - probability is 2**(-31). R += D. Q[digit] -= 1 + quotient[rIndex - divisorLength] = qhat - 1; + int carry = 0; + int dIndex = 0; + for (iulRwork = rIndex - divisorLength; dIndex < divisorLength; dIndex++, iulRwork++) { + long accum2 = (divisor[dIndex] & LONG_MASK) + + (remainder[iulRwork] & LONG_MASK) + (carry & LONG_MASK); + carry = extractHiInt(accum2); + remainder[iulRwork] = extractLowInt(accum2); + } + remainder[iulRwork] += carry; + } + } + + // D8. Unnormalize: Divide R to get result + if (d1 > 1) { + divideMultiPrecision(remainder, d1); + } + + return remainder; + } + + static { + BIT_LENGTH = new byte[256]; + BIT_LENGTH[0] = 0; + for (int i = 1; i < 8; ++i) { + for (int j = 1 << (i - 1); j < 1 << i; ++j) { + BIT_LENGTH[j] = (byte) i; + } + } + + POWER_FIVES_INT31[0] = 1; + for (int i = 1; i < POWER_FIVES_INT31.length; ++i) { + POWER_FIVES_INT31[i] = POWER_FIVES_INT31[i - 1] * 5; + assert (POWER_FIVES_INT31[i] > 0); + } + + POWER_FIVES_INT63[0] = 1L; + for (int i = 1; i < POWER_FIVES_INT63.length; ++i) { + POWER_FIVES_INT63[i] = POWER_FIVES_INT63[i - 1] * 5L; + assert (POWER_FIVES_INT63[i] > 0L); + } + + POWER_TENS_INT31[0] = 1; + ROUND_POWER_TENS_INT31[0] = 0; + for (int i = 1; i < POWER_TENS_INT31.length; ++i) { + POWER_TENS_INT31[i] = POWER_TENS_INT31[i - 1] * 10; + assert (POWER_TENS_INT31[i] > 0); + ROUND_POWER_TENS_INT31[i] = POWER_TENS_INT31[i] >> 1; + } + + POWER_FIVES_INT128[0] = new UnsignedInt128(1); + INVERSE_POWER_FIVES_INT128[0] = new UnsignedInt128(0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF); + for (int i = 1; i < POWER_FIVES_INT128.length; ++i) { + POWER_FIVES_INT128[i] = new UnsignedInt128(POWER_FIVES_INT128[i - 1]); + POWER_FIVES_INT128[i].multiplyDestructive(5); + INVERSE_POWER_FIVES_INT128[i] = new UnsignedInt128( + INVERSE_POWER_FIVES_INT128[i - 1]); + INVERSE_POWER_FIVES_INT128[i].divideDestructive(5); + } + + POWER_TENS_INT128[0] = new UnsignedInt128(1); + ROUND_POWER_TENS_INT128[0] = new UnsignedInt128(0); + INVERSE_POWER_TENS_INT128[0] = new UnsignedInt128(0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF); + INVERSE_POWER_TENS_INT128_WORD_SHIFTS[0] = 0; + int[] inverseTens = new int[8]; + Arrays.fill(inverseTens, 0xFFFFFFFF); + for (int i = 1; i < POWER_TENS_INT128.length; ++i) { + final int divisor = 10; + POWER_TENS_INT128[i] = new UnsignedInt128(POWER_TENS_INT128[i - 1]); + POWER_TENS_INT128[i].multiplyDestructive(divisor); + ROUND_POWER_TENS_INT128[i] = POWER_TENS_INT128[i].shiftRightConstructive( + 1, false); + + long quotient; + long remainder = 0; + for (int j = inverseTens.length - 1; j >= 0; --j) { + quotient = ((inverseTens[j] & SqlMathUtil.LONG_MASK) + (remainder << 32)) + / divisor; + remainder = ((inverseTens[j] & SqlMathUtil.LONG_MASK) + (remainder << 32)) + % divisor; + inverseTens[j] = (int) quotient; + } + int wordShifts = 0; + for (int j = inverseTens.length - 1; j >= 4 && inverseTens[j] == 0; --j) { + ++wordShifts; + } + INVERSE_POWER_TENS_INT128_WORD_SHIFTS[i] = wordShifts; + INVERSE_POWER_TENS_INT128[i] = new UnsignedInt128( + inverseTens[inverseTens.length - 4 - wordShifts], + inverseTens[inverseTens.length - 3 - wordShifts], + inverseTens[inverseTens.length - 2 - wordShifts], + inverseTens[inverseTens.length - 1 - wordShifts]); + } + } + + private SqlMathUtil() { + } +} diff --git a/common/src/java/org/apache/hadoop/hive/common/type/UnsignedInt128.java b/common/src/java/org/apache/hadoop/hive/common/type/UnsignedInt128.java new file mode 100644 index 0000000..d71ebb3 --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/type/UnsignedInt128.java @@ -0,0 +1,2387 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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; +import java.util.Arrays; +import org.apache.hadoop.hive.common.type.SqlMathUtil; + +/** + * This code was originally written for Microsoft PolyBase. + * + * Represents an unsigned 128-bit integer. This is the basis for + * {@link Decimal128} and {@link SignedInt128}. This object is much faster and + * more compact than BigInteger, but has many limitations below. + * + */ +public final class UnsignedInt128 implements Comparable { + + /** Number of ints to store this object. */ + public static final int INT_COUNT = 4; + + /** Number of bytes to store this object. */ + public static final int BYTE_SIZE = 4 * INT_COUNT; + + /** Can hold up to 10^38. */ + public static final int MAX_DIGITS = 38; + + /** Maximum value that can be represented in this class. */ + public static final UnsignedInt128 MAX_VALUE = new UnsignedInt128(0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + + /** Minimum value that can be represented in this class. */ + public static final UnsignedInt128 MIN_VALUE = new UnsignedInt128(0); + + /** A special value representing 10**38. */ + public static final UnsignedInt128 TEN_TO_THIRTYEIGHT = new UnsignedInt128(0, + 0x98a2240, 0x5a86c47a, 0x4b3b4ca8); + + /** + * Int32 elements as little-endian (v[0] is least significant) unsigned + * integers. + */ + private final int[] v = new int[INT_COUNT]; + + /** + * Number of leading non-zero elements in {@link #v}. For example, if the + * value of this object is 123 (v0=123, v1=v2=v3=0), then 1. 0 to 4. 0 means + * that this object represents zero. + * + * @see #updateCount() + */ + private transient byte count; + + /** + * 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) { + assert (precision >= 0 && precision <= 38); + if (precision <= 9) { + return 1; + } else if (precision <= 19) { + return 2; + } else if (precision <= 28) { + return 3; + } + return 4; + } + + /** Creates an instance that represents zero. */ + public UnsignedInt128() { + zeroClear(); + } + + /** + * Copy constructor. + * + * @param o + * The instance to copy from + */ + public UnsignedInt128(UnsignedInt128 o) { + update(o); + } + + /** + * Creates an instance that has the given values. + * + * @param v0 + * v0 + * @param v1 + * v1 + * @param v2 + * v2 + * @param v3 + * v3 + */ + public UnsignedInt128(int v0, int v1, int v2, int v3) { + this.v[0] = v0; + this.v[1] = v1; + this.v[2] = v2; + this.v[3] = v3; + updateCount(); + } + + /** + * Constructs from the given long value. + * + * @param v + * long value + */ + public UnsignedInt128(long v) { + update(v); + } + + /** + * Constructs from the given string. + * + * @param str + * string + */ + public UnsignedInt128(String str) { + update(str); + } + + /** + * Constructs from the given string with given offset and length. + * + * @param str + * string + * @param offset + * offset + * @param length + * length + */ + public UnsignedInt128(char[] str, int offset, int length) { + update(str, offset, length); + } + + /** @return v[0] */ + public int getV0() { + return v[0]; + } + + /** @return v[1] */ + public int getV1() { + return v[1]; + } + + /** @return v[2] */ + public int getV2() { + return v[2]; + } + + /** @return v[3] */ + public int getV3() { + return v[3]; + } + + /** + * Setter for v0. + * + * @param val + * value to set + */ + public void setV0(int val) { + v[0] = val; + updateCount(); + } + + /** + * Setter for v1. + * + * @param val + * value to set + */ + public void setV1(int val) { + v[1] = val; + updateCount(); + } + + /** + * Setter for v2. + * + * @param val + * value to set + */ + public void setV2(int val) { + v[2] = val; + updateCount(); + } + + /** + * Setter for v3. + * + * @param val + * value to set + */ + public void setV3(int val) { + v[3] = val; + updateCount(); + } + + /** + * Returns if we overflowed 10**38, but not 2**128. This code is equivalent to + * CSsNumeric::FGt10_38 in SQLServer (numeric.cpp). However, be aware that the + * elements are signed ints, not UI4 in SQLServer. + * + * @return whether this value is equal to or larger than 10**38 + */ + public boolean exceedsTenToThirtyEight() { + + // 10**38= + // v[0]=0(0),v[1]=160047680(98a2240),v[2]=1518781562(5a86c47a),v[3]=1262177448(4b3b4ca8) + + // check most significant part first + if (v[3] != 0x4b3b4ca8) { + return (v[3] < 0 || v[3] > 0x4b3b4ca8); + } + + // check second most significant part + if (v[2] != 0x5a86c47a) { + return (v[2] < 0 || v[2] > 0x5a86c47a); + } + + return (v[1] < 0 || v[1] > 0x098a2240); + } + + /** + * Used to check overflows. This is NOT used in {@link UnsignedInt128} itself + * because this overflow semantics is Decimal's. (throws - but not a checked + * exception) ArithmeticException if this value is equal to or exceed 10**38. + */ + public void throwIfExceedsTenToThirtyEight() { + if (exceedsTenToThirtyEight()) { + SqlMathUtil.throwOverflowException(); + } + } + + /** + * Returns the value of this object as long, throwing error if the value + * exceeds long. + * + * @return the value this object represents + */ + public long asLong() { + if (this.count > 2 || v[1] < 0) { + SqlMathUtil.throwOverflowException(); + } + return (((long) v[1]) << 32L) | v[0]; + } + + /** Make the value to zero. */ + public void zeroClear() { + this.v[0] = 0; + this.v[1] = 0; + this.v[2] = 0; + this.v[3] = 0; + this.count = 0; + } + + /** @return whether the value is zero */ + public boolean isZero() { + return this.count == 0; + } + + /** @return whether the value is one */ + public boolean isOne() { + return this.v[0] == 1 && this.count == 1; + } + + /** @return whether 32bits int is enough to represent this value */ + public boolean fitsInt32() { + return this.count <= 1; + } + + /** + * Copy from the given object. + * + * @param o + * The instance to copy from + */ + public void update(UnsignedInt128 o) { + update(o.v[0], o.v[1], o.v[2], o.v[3]); + } + + /** + * Updates the value of this object with the given long value. + * + * @param v + * long value + */ + public void update(long v) { + assert (v >= 0); + update((int) v, (int) (v >> 32), 0, 0); + } + + /** + * Updates the value of this object with the given values. + * + * @param v0 + * v0 + * @param v1 + * v1 + * @param v2 + * v2 + * @param v3 + * v3 + */ + public void update(int v0, int v1, int v2, int v3) { + this.v[0] = v0; + this.v[1] = v1; + this.v[2] = v2; + this.v[3] = v3; + updateCount(); + } + + /** + * Updates the value of this object by reading from ByteBuffer, using the + * required number of ints for the given precision. + * + * @param buf + * ByteBuffer to read values from + * @param precision + * 0 to 38. Decimal digits. + */ + public void update(IntBuffer buf, int precision) { + switch (getIntsPerElement(precision)) { + case 1: + update32(buf); + break; + case 2: + update64(buf); + break; + case 3: + update96(buf); + break; + case 4: + update128(buf); + break; + default: + throw new RuntimeException(); + } + } + + /** + * 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) { + buf.get(v, 0, INT_COUNT); + updateCount(); + } + + /** + * 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) { + buf.get(v, 0, 3); + v[3] = 0; + updateCount(); + } + + /** + * 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) { + buf.get(v, 0, 2); + v[2] = 0; + v[3] = 0; + updateCount(); + } + + /** + * 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) { + v[0] = buf.get(); + v[1] = 0; + v[2] = 0; + v[3] = 0; + updateCount(); + } + + /** + * Updates the value of this object by reading from the given array, using the + * required number of ints for the given precision. + * + * @param array + * array to read values from + * @param offset + * offset of the long array + * @param precision + * 0 to 38. Decimal digits. + */ + public void update(int[] array, int offset, int precision) { + switch (getIntsPerElement(precision)) { + case 1: + update32(array, offset); + break; + case 2: + update64(array, offset); + break; + case 3: + update96(array, offset); + break; + case 4: + update128(array, offset); + break; + default: + throw new RuntimeException(); + } + } + + /** + * 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 long array + */ + public void update128(int[] array, int offset) { + System.arraycopy(array, offset, v, 0, 4); + updateCount(); + } + + /** + * 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 long array + */ + public void update96(int[] array, int offset) { + System.arraycopy(array, offset, v, 0, 3); + v[3] = 0; + updateCount(); + } + + /** + * 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 long array + */ + public void update64(int[] array, int offset) { + System.arraycopy(array, offset, v, 0, 2); + v[2] = 0; + v[3] = 0; + updateCount(); + } + + /** + * 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 long array + */ + public void update32(int[] array, int offset) { + v[0] = array[offset]; + v[1] = 0; + v[2] = 0; + v[3] = 0; + updateCount(); + } + + /** + * 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) { + + // Skip leading zeros and compute number of digits in magnitude + final int end = offset + length; + assert (end <= str.length); + int cursor = offset; + while (cursor < end && str[cursor] == '0') { + ++cursor; + } + + if (cursor == end) { + zeroClear(); + return; + } + if (end - cursor > MAX_DIGITS) { + SqlMathUtil.throwOverflowException(); + } + + int accumulated = 0; + int accumulatedCount = 0; + while (cursor < end) { + if (str[cursor] < '0' || str[cursor] > '9') { + throw new NumberFormatException("Invalid string:" + + new String(str, offset, length)); + } + + if (accumulatedCount == 9) { + scaleUpTenDestructive((short) accumulatedCount); + addDestructive(accumulated); + accumulated = 0; + accumulatedCount = 0; + } + int digit = str[cursor] - '0'; + accumulated = accumulated * 10 + digit; + ++accumulatedCount; + ++cursor; + } + + if (accumulatedCount > 0) { + scaleUpTenDestructive((short) accumulatedCount); + addDestructive(accumulated); + } + } + + /** + * Serialize this object to the given ByteBuffer, putting the required number + * of ints for the given precision. + * + * @param buf + * ByteBuffer to write values to + * @param precision + * 0 to 38. Decimal digits. + */ + public void serializeTo(IntBuffer buf, int precision) { + buf.put(v, 0, getIntsPerElement(precision)); + } + + /** + * Serialize this object to the given ByteBuffer, putting 128 bits data (full + * ranges). + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo128(IntBuffer buf) { + buf.put(v, 0, 4); + } + + /** + * Serialize this object to the given ByteBuffer, putting only 96 bits data. + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo96(IntBuffer buf) { + assert (v[3] == 0); + buf.put(v, 0, 3); + } + + /** + * Serialize this object to the given ByteBuffer, putting only 64 bits data. + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo64(IntBuffer buf) { + assert (v[2] == 0); + assert (v[3] == 0); + buf.put(v, 0, 2); + } + + /** + * Serialize this object to the given ByteBuffer, putting only 32 bits data. + * + * @param buf + * ByteBuffer to write values to + */ + public void serializeTo32(IntBuffer buf) { + assert (v[1] == 0); + assert (v[2] == 0); + assert (v[3] == 0); + buf.put(v[0]); + } + + /** + * Serialize this object to the given array, putting the required number of + * ints for the given precision. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + * @param precision + * 0 to 38. Decimal digits. + */ + public void serializeTo(int[] array, int offset, int precision) { + System.arraycopy(v, 0, array, offset, getIntsPerElement(precision)); + } + + /** + * Serialize this object to the given array, putting 128 bits data (full + * ranges). + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo128(int[] array, int offset) { + System.arraycopy(v, 0, array, offset, 4); + } + + /** + * Serialize this object to the given array, putting only 96 bits data. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo96(int[] array, int offset) { + assert (v[3] == 0); + System.arraycopy(v, 0, array, offset, 3); + } + + /** + * Serialize this object to the given array, putting only 64 bits data. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo64(int[] array, int offset) { + assert (v[2] == 0); + assert (v[3] == 0); + System.arraycopy(v, 0, array, offset, 2); + } + + /** + * Serialize this object to the given array, putting only 32 bits data. + * + * @param array + * array to write values to + * @param offset + * offset of the int array + */ + public void serializeTo32(int[] array, int offset) { + assert (v[1] == 0); + assert (v[2] == 0); + assert (v[3] == 0); + array[0] = v[0]; + } + + @Override + public int compareTo(UnsignedInt128 o) { + return compareTo(o.v); + } + + /** + * @see #compareTo(UnsignedInt128) + * @param o + * the object to be compared. + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified object. + */ + public int compareTo(int[] o) { + return compareTo(o[0], o[1], o[2], o[3]); + } + + /** + * @see #compareTo(UnsignedInt128) + * @param o0 + * o0 + * @param o1 + * o1 + * @param o2 + * o2 + * @param o3 + * o3 + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified object. + */ + public int compareTo(int o0, int o1, int o2, int o3) { + if (v[3] != o3) { + return SqlMathUtil.compareUnsignedInt(v[3], o3); + } else if (v[2] != o2) { + return SqlMathUtil.compareUnsignedInt(v[2], o2); + } else if (v[1] != o1) { + return SqlMathUtil.compareUnsignedInt(v[1], o1); + } else { + return SqlMathUtil.compareUnsignedInt(v[0], o0); + } + } + + /** + * Compares with the given object after scaling up/down it for 10**scaleUp. + * This method is not destructive. Used from {@link Decimal128}. + * + * @param o + * the object to compare with + * @param tenScale + * power of 10 to scale up (if positive) or down (if negative) the + * given object. + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified object. + */ + public int compareToScaleTen(UnsignedInt128 o, short tenScale) { + + // easier case. take a quick path + if (tenScale == 0) { + return compareTo(o); + } + + // if o is zero, easy. + if (o.isZero()) { + return this.isZero() ? 0 : 1; + } else if (this.isZero()) { + + // if this is zero, we need to check if o might become zero after + // scaling down. + // this is not easy because there might be rounding. + if (tenScale > 0) { + + // scaling up, so o is definitely larger + return -1; + } + if (tenScale < -SqlMathUtil.MAX_POWER_TEN_INT128) { + + // any value will become zero. even no possibility of rounding + return 0; + } else { + + // compare with 5 * 10**-tenScale + // example: tenScale=-1. o will be zero after scaling if o>=5. + boolean oZero = o + .compareTo(SqlMathUtil.ROUND_POWER_TENS_INT128[-tenScale]) < 0; + return oZero ? 0 : -1; + } + } + + // another quick path + if (this.fitsInt32() && o.fitsInt32() + && tenScale <= SqlMathUtil.MAX_POWER_TEN_INT31) { + long v0Long = this.v[0] & SqlMathUtil.LONG_MASK; + long o0; + if (tenScale < 0) { + if (tenScale < -SqlMathUtil.MAX_POWER_TEN_INT31) { + + // this scales down o.v[0] to 0 because 2^32 = 4.2E9. No + // possibility of rounding. + o0 = 0L; + } else { + + // divide by 10**-tenScale. check for rounding. + o0 = (o.v[0] & SqlMathUtil.LONG_MASK) + / SqlMathUtil.POWER_TENS_INT31[-tenScale]; + long remainder = (o.v[0] & SqlMathUtil.LONG_MASK) + % SqlMathUtil.POWER_TENS_INT31[-tenScale]; + if (remainder >= SqlMathUtil.ROUND_POWER_TENS_INT31[-tenScale]) { + assert (o0 >= 0); + ++o0; // this is safe because o0 is positive + } + } + } else { + + // tenScale <= SqlMathUtil.MAX_POWER_TEN_INT31 + // so, we can make this as a long comparison + o0 = (o.v[0] & SqlMathUtil.LONG_MASK) + * (SqlMathUtil.POWER_TENS_INT31[tenScale] & SqlMathUtil.LONG_MASK); + } + return SqlMathUtil.compareUnsignedLong(v0Long, o0); + } + + // unfortunately no quick path. let's do scale up/down + int[] ov = o.v.clone(); + if (tenScale < 0) { + + // scale down. does rounding + scaleDownTenArray4RoundUp(ov, (short) -tenScale); + } else { + + // scale up + boolean overflow = scaleUpTenArray(ov, tenScale); + if (overflow) { + + // overflow is not an error here. it just means "this" is + // smaller + return -1; + } + } + + return compareTo(ov); + } + + @Override + public int hashCode() { + + // note: v[0] ^ v[1] ^ v[2] ^ v[3] would cause too many hash collisions + return (v[0] * 0x2AB19E23) + (v[1] * 0x4918EACB) + (v[2] * 0xF03051A7) + + v[3]; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof UnsignedInt128)) { + return false; + } + return equals((UnsignedInt128) obj); + } + + /** + * Specialized version. + * + * @see #equals(Object) + * @param o + * the object to compare with + * @return whether this object is equal to the given object + */ + public boolean equals(UnsignedInt128 o) { + return this.v[0] == o.v[0] && this.v[1] == o.v[1] && this.v[2] == o.v[2] + && this.v[3] == o.v[3]; + } + + /** + * Specialized version. + * + * @see #equals(Object) + * @param o0 + * o0 + * @param o1 + * o1 + * @param o2 + * o2 + * @param o3 + * o3 + * @return whether this object is equal to the given object + */ + public boolean equals(int o0, int o1, int o2, int o3) { + return this.v[0] == o0 && this.v[1] == o1 && this.v[2] == o2 + && this.v[3] == o3; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return new UnsignedInt128(this); + } + + /** + * 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 = BigInteger.valueOf(v[3] & SqlMathUtil.LONG_MASK); + bigInt = bigInt.shiftLeft(32); + bigInt = bigInt.add(BigInteger.valueOf(v[2] & SqlMathUtil.LONG_MASK)); + bigInt = bigInt.shiftLeft(32); + bigInt = bigInt.add(BigInteger.valueOf(v[1] & SqlMathUtil.LONG_MASK)); + bigInt = bigInt.shiftLeft(32); + bigInt = bigInt.add(BigInteger.valueOf(v[0] & SqlMathUtil.LONG_MASK)); + return 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() { + char[] buf = new char[MAX_DIGITS + 1]; + int bufCount = 0; + int nonZeroBufCount = 0; + + final int tenScale = SqlMathUtil.MAX_POWER_TEN_INT31; + final int tenPower = SqlMathUtil.POWER_TENS_INT31[tenScale]; + UnsignedInt128 tmp = new UnsignedInt128(this); + + while (!tmp.isZero()) { + int remainder = tmp.divideDestructive(tenPower); + for (int i = 0; i < tenScale && bufCount < buf.length; ++i) { + int digit = remainder % 10; + remainder /= 10; + buf[bufCount] = (char) (digit + '0'); + ++bufCount; + if (digit != 0) { + nonZeroBufCount = bufCount; + } + } + } + + if (bufCount == 0) { + return "0"; + } else { + char[] reversed = new char[nonZeroBufCount]; + for (int i = 0; i < nonZeroBufCount; ++i) { + reversed[i] = buf[nonZeroBufCount - i - 1]; + } + return new String(reversed); + } + } + + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + str.append("Int128: count=" + count + ","); + str.append("v[0]=" + v[0] + "(0x" + Integer.toHexString(v[0]) + "), "); + str.append("v[1]=" + v[1] + "(0x" + Integer.toHexString(v[1]) + "), "); + str.append("v[2]=" + v[2] + "(0x" + Integer.toHexString(v[2]) + "), "); + str.append("v[3]=" + v[3] + "(0x" + Integer.toHexString(v[3]) + "), "); + str.append("BigInteger#toString=" + toBigIntegerSlow().toString()); + return new String(str); + } + + /** + * Adds the given value to this value. This version is destructive, meaning it + * modifies this object. + * + * @param right + * the value to add + */ + public void addDestructive(UnsignedInt128 right) { + addDestructive(right.v); + } + + /** + * Adds the given value to this value. This version is destructive, meaning it + * modifies this object. + * + * @param r + * the value to add + */ + public void addDestructive(int[] r) { + long sum = 0L; + for (int i = 0; i < INT_COUNT; ++i) { + sum = (this.v[i] & SqlMathUtil.LONG_MASK) + + (r[i] & SqlMathUtil.LONG_MASK) + (sum >>> 32); + this.v[i] = (int) sum; + } + updateCount(); + + if ((sum >> 32) != 0) { + SqlMathUtil.throwOverflowException(); + } + } + + /** + * Adds the given value to this value. This version is destructive, meaning it + * modifies this object. + * + * @param r + * the value to add + */ + public void addDestructive(int r) { + if ((this.v[0] & SqlMathUtil.LONG_MASK) + (r & SqlMathUtil.LONG_MASK) >= (1L << 32L)) { + this.v[0] += r; + + if (this.v[1] == SqlMathUtil.FULLBITS_32) { + this.v[1] = 0; + if (this.v[2] == SqlMathUtil.FULLBITS_32) { + this.v[2] = 0; + if (this.v[3] == SqlMathUtil.FULLBITS_32) { + SqlMathUtil.throwOverflowException(); + } else { + ++this.v[3]; + } + } else { + ++this.v[2]; + } + } else { + ++this.v[1]; + } + } else { + this.v[0] += r; + } + updateCount(); + } + + /** + * Adds one to this value. This version is destructive, meaning it modifies + * this object. + */ + public void incrementDestructive() { + incrementArray(v); + updateCount(); + } + + /** + * Subtracts one from this value. This version is destructive, meaning it + * modifies this object. + */ + public void decrementDestructive() { + decrementArray(v); + updateCount(); + } + + /** + * Adds the given value after scaling to this value. this := this + (right * + * 10**tenScale). This version is destructive, meaning it modifies this + * object. + * + * @param right + * the value to add + * @param tenScale + * number of ten-based scaling. could be either positive or negative. + */ + public void addDestructiveScaleTen(UnsignedInt128 right, short tenScale) { + if (tenScale == 0) { + addDestructive(right); + return; + } + + // scale up/down + final int[] r = right.v.clone(); + if (tenScale < 0) { + + // scale down + scaleDownTenArray4RoundUp(r, (short) -tenScale); + } else if (tenScale > 0) { + + // scale up + boolean overflow = scaleUpTenArray(r, tenScale); + if (overflow) { + SqlMathUtil.throwOverflowException(); + } + } + + addDestructive(r); + } + + /** + * Subtracts the given value from this value. In other words, this := this - + * right. This method will throw overflow exception if right operand is larger + * than this value. This version is destructive, meaning it modifies this + * object. + * + * @param right + * the value to subtract + */ + public void subtractDestructive(UnsignedInt128 right) { + subtractDestructive(right.v); + } + + /** + * Subtracts the given value from this value. In other words, this := this - + * right. This method doesn't work if right operand is larger than this value. + * This version is destructive, meaning it modifies this object. + * + * @param r + * the value to subtract + */ + public void subtractDestructive(int[] r) { + long sum = 0L; + for (int i = 0; i < INT_COUNT; ++i) { + sum = (this.v[i] & SqlMathUtil.LONG_MASK) + - (r[i] & SqlMathUtil.LONG_MASK) - ((int) -(sum >> 32)); + this.v[i] = (int) sum; + } + updateCount(); + + if ((sum >> 32) != 0) { + SqlMathUtil.throwOverflowException(); + } + } + + /** + * Calculates absolute difference (remember that this is unsigned) of left and + * right operator. result := abs (left - right) This is the core + * implementation of subtract and signed add. + * + * @param left + * left operand + * @param right + * right operand + * @param result + * the object to receive the result. can be same object as left or + * right. + * @return signum of the result. -1 if left was smaller than right, 1 if + * larger, 0 if same (result is zero). + */ + public static byte difference(UnsignedInt128 left, UnsignedInt128 right, + UnsignedInt128 result) { + return differenceInternal(left, right.v, result); + } + + /** + * Calculates absolute difference of left and right operator after ten-based + * scaling on right. + * result := abs (left - (right * 10**tenScale)) This is the core + * implementation of subtract. + * + * @param left + * left operand + * @param right + * right operand + * @param result + * the object to receive the result. can be same object as left or + * right. + * @param tenScale + * number of ten-based scaling. could be either positive or negative. + * @return signum of the result. -1 if left was smaller than right, 1 if + * larger, 0 if same (result is zero). + */ + public static byte differenceScaleTen(UnsignedInt128 left, + UnsignedInt128 right, UnsignedInt128 result, short tenScale) { + if (tenScale == 0) { + return difference(left, right, result); + } + + // scale up/down + int[] r = right.v.clone(); + if (tenScale < 0) { + // scale down + scaleDownTenArray4RoundUp(r, (short) -tenScale); + } else { + // scale up + boolean overflow = scaleUpTenArray(r, tenScale); + if (overflow) { + SqlMathUtil.throwOverflowException(); + } + } + + return differenceInternal(left, r, result); + } + + /** + * Multiplies this value with the given integer value. This version is + * destructive, meaning it modifies this object. + * + * @param right + * the value to multiply + */ + public void multiplyDestructive(int right) { + if (right == 0) { + zeroClear(); + return; + } else if (right == 1) { + return; + } + + long sum = 0L; + long rightUnsigned = right & SqlMathUtil.LONG_MASK; + for (int i = 0; i < INT_COUNT; ++i) { + sum = (this.v[i] & SqlMathUtil.LONG_MASK) * rightUnsigned + (sum >>> 32); + this.v[i] = (int) sum; + } + updateCount(); + + if ((sum >> 32) != 0) { + SqlMathUtil.throwOverflowException(); + } + } + + /** + * Multiplies this value with the given value. This version is destructive, + * meaning it modifies this object. + * + * @param right + * the value to multiply + */ + public void multiplyDestructive(UnsignedInt128 right) { + if (this.fitsInt32() && right.fitsInt32()) { + multiplyDestructiveFitsInt32(right, (short) 0, (short) 0); + return; + } + multiplyArrays4And4To4NoOverflow(this.v, right.v); + updateCount(); + } + + /** + * Multiplies this value with the given value, followed by right bit shifts to + * scale it back to this object. This is used from division. This version is + * destructive, meaning it modifies this object. + * + * @param right + * the value to multiply + * @param rightShifts + * the number of right-shifts after multiplication + */ + public void multiplyShiftDestructive(UnsignedInt128 right, short rightShifts) { + if (this.fitsInt32() && right.fitsInt32()) { + multiplyDestructiveFitsInt32(right, rightShifts, (short) 0); + return; + } + + int[] z = multiplyArrays4And4To8(this.v, right.v); + shiftRightArray(rightShifts, z, this.v, true); + updateCount(); + } + + /** + * Multiply this value with the given value, followed by ten-based scale down. + * This method does the two operations without cutting off, so it preserves + * accuracy without throwing a wrong overflow exception. + * + * @param right + * right operand + * @param tenScale + * distance to scale down + */ + public void multiplyScaleDownTenDestructive(UnsignedInt128 right, + short tenScale) { + assert (tenScale >= 0); + if (this.fitsInt32() && right.fitsInt32()) { + multiplyDestructiveFitsInt32(right, (short) 0, tenScale); + return; + } + int[] z = multiplyArrays4And4To8(this.v, right.v); + + // Then, scale back. + scaleDownTenArray8RoundUp(z, tenScale); + update(z[0], z[1], z[2], z[3]); + } + + /** + * Divides this value with the given value. This version is destructive, + * meaning it modifies this object. + * + * @param right + * the value to divide + * @param remainder + * object to receive remainder + */ + public void divideDestructive(UnsignedInt128 right, UnsignedInt128 remainder) { + if (right.isZero()) { + assert (right.isZero()); + SqlMathUtil.throwZeroDivisionException(); + } + if (right.count == 1) { + assert (right.v[1] == 0); + assert (right.v[2] == 0); + assert (right.v[3] == 0); + int rem = divideDestructive(right.v[0]); + remainder.update(rem); + return; + } + + int[] quotient = new int[5]; + int[] rem = SqlMathUtil.divideMultiPrecision(this.v, right.v, quotient); + + update(quotient[0], quotient[1], quotient[2], quotient[3]); + remainder.update(rem[0], rem[1], rem[2], rem[3]); + } + + /** + * Scale up this object for 10**tenScale and then divides this value with the + * given value. This version is destructive, meaning it modifies this object. + * + * @param right + * the value to divide + * @param tenScale + * ten-based scale up distance + * @param remainder + * object to receive remainder + */ + public void divideScaleUpTenDestructive(UnsignedInt128 right, short tenScale, + UnsignedInt128 remainder) { + // in this case, we have to scale up _BEFORE_ division. otherwise we + // might lose precision. + if (tenScale > SqlMathUtil.MAX_POWER_TEN_INT128) { + // in this case, the result will be surely more than 128 bit even + // after division + SqlMathUtil.throwOverflowException(); + } + int[] scaledUp = multiplyConstructive256(SqlMathUtil.POWER_TENS_INT128[tenScale]); + int[] quotient = new int[5]; + int[] rem = SqlMathUtil.divideMultiPrecision(scaledUp, right.v, quotient); + update(quotient[0], quotient[1], quotient[2], quotient[3]); + remainder.update(rem[0], rem[1], rem[2], rem[3]); + } + + /** + * 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) { + assert (right >= 0); + long rightUnsigned = right & SqlMathUtil.LONG_MASK; + + long quotient; + long remainder = 0; + + for (int i = INT_COUNT - 1; i >= 0; --i) { + remainder = ((this.v[i] & SqlMathUtil.LONG_MASK) + (remainder << 32)); + quotient = remainder / rightUnsigned; + remainder %= rightUnsigned; + this.v[i] = (int) quotient; + } + updateCount(); + return (int) remainder; + } + + /** + * Right-shift for the given number of bits. This version is destructive, + * meaning it modifies this object. + * + * @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) { + assert (bits >= 0); + shiftRightDestructive(bits / 32, bits % 32, roundUp); + } + + /** + * Left-shift for the given number of bits. 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) { + assert (bits >= 0); + shiftLeftDestructive(bits / 32, bits % 32); + } + + /** + * Left-shift for the given number of bits. This method throws 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 shiftLeftDestructiveCheckOverflow(int bits) { + if (bitLength() + bits >= 128) { + SqlMathUtil.throwOverflowException(); + } + shiftLeftDestructive(bits); + } + + /** + * 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) { + if (tenScale == 0) { + return; + } + + if (tenScale < 0) { + throw new IllegalArgumentException(); + } + + if (isZero()) { + return; + } + + scaleDownTenArray4RoundUp(v, tenScale); + updateCount(); + } + + /** + * Scale down the value for 5**tenScale (this := this / 5**tenScale). This + * method rounds-up, eg 42/5=8, 43/5=9. This version is destructive, meaning + * it modifies this object. + * + * @param fiveScale + * scaling. must be positive + */ + public void scaleDownFiveDestructive(short fiveScale) { + if (fiveScale == 0) { + return; + } + + if (fiveScale < 0) { + throw new IllegalArgumentException(); + } + + if (isZero()) { + return; + } + + scaleDownFiveArrayRoundUp(v, fiveScale); + updateCount(); + } + + /** + * 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) { + if (tenScale == 0) { + return; + } + + if (tenScale < 0) { + throw new IllegalArgumentException(); + } + + if (isZero()) { + return; + } + + // First, scale up with 2. Check overflow + shiftLeftDestructiveCheckOverflow(tenScale); + + // Then, scale up with 5 + scaleUpFiveDestructive(tenScale); + } + + /** + * Scale up the value for 5**tenScale (this := this * 5**tenScale). Scaling up + * DOES throw an error when an overflow occurs. This version is destructive, + * meaning it modifies this object. + * + * @param fiveScale + * scaling. must be positive + */ + public void scaleUpFiveDestructive(short fiveScale) { + if (fiveScale == 0) { + return; + } + + if (fiveScale < 0) { + throw new IllegalArgumentException(); + } + + if (isZero()) { + return; + } + + // Scale up with 5. This is done via #multiplyDestructive(int). + while (fiveScale > 0) { + int powerFive = Math.min(fiveScale, SqlMathUtil.MAX_POWER_FIVE_INT31); + multiplyDestructive(SqlMathUtil.POWER_FIVES_INT31[powerFive]); + fiveScale -= powerFive; + } + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. + * + * @param right + * right operand + * @return operation result as a new object + */ + public UnsignedInt128 addConstructive(UnsignedInt128 right) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.addDestructive(right); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. + * + * @return operation result as a new object + */ + public UnsignedInt128 incrementConstructive() { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.incrementDestructive(); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. This method doesn't work if right operand is larger than this + * value. + * + * @param right + * right operand + * @return operation result as a new object + */ + public UnsignedInt128 subtractConstructive(UnsignedInt128 right) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.subtractDestructive(right); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. This method doesn't work if right operand is larger than this + * value. + * + * @return operation result as a new object + */ + public UnsignedInt128 decrementConstructive() { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.decrementDestructive(); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. + * + * @param right + * right operand + * @return operation result as a new object + */ + public UnsignedInt128 multiplyConstructive(int right) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.multiplyDestructive(right); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. + * + * @param right + * right operand + * @return operation result as a new object + */ + public UnsignedInt128 multiplyConstructive(UnsignedInt128 right) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.multiplyDestructive(right); + return ret; + } + + /** + * This version returns the result of multiplication as 256bit data. + * + * @param right + * right operand + * @return operation result as 256bit data + */ + public int[] multiplyConstructive256(UnsignedInt128 right) { + return multiplyArrays4And4To8(this.v, right.v); + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. Note that this method cannot receive remainder. Use destructive + * version for it. + * + * @param right + * right operand + * @return operation result as a new object + */ + public UnsignedInt128 divideConstructive(int right) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.divideDestructive(right); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. + * + * @param right + * right operand + * @param remainder + * object to receive remainder + * @return operation result as a new object + */ + public UnsignedInt128 divideConstructive(UnsignedInt128 right, + UnsignedInt128 remainder) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.divideDestructive(right, remainder); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. + * + * @param bits + * the number of bits. must be positive + * @param roundUp + * whether to round up the most significant bit that was discarded + * @return operation result as a new object + */ + public UnsignedInt128 shiftRightConstructive(int bits, boolean roundUp) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.shiftRightDestructive(bits, roundUp); + return ret; + } + + /** + * This version returns the result as a new object, not modifying the give + * objects. + * + * @param bits + * the number of bits. must be positive + * @return operation result as a new object + */ + public UnsignedInt128 shiftLeftConstructive(int bits) { + UnsignedInt128 ret = new UnsignedInt128(this); + ret.shiftLeftDestructive(bits); + return ret; + } + + private short bitLength() { + return SqlMathUtil.bitLength(v[0], v[1], v[2], v[3]); + } + + private void shiftRightDestructive(int wordShifts, int bitShiftsInWord, + boolean roundUp) { + if (wordShifts == 0 && bitShiftsInWord == 0) { + return; + } + + assert (wordShifts >= 0); + assert (bitShiftsInWord >= 0); + assert (bitShiftsInWord < 32); + if (wordShifts >= 4) { + zeroClear(); + return; + } + + final int shiftRestore = 32 - bitShiftsInWord; + + // check this because "123 << 32" will be 123. + final boolean noRestore = bitShiftsInWord == 0; + final int roundCarryNoRestoreMask = 1 << 31; + final int roundCarryMask = (1 << (bitShiftsInWord - 1)); + boolean roundCarry; + int z0 = 0, z1 = 0, z2 = 0, z3 = 0; + + switch (wordShifts) { + case 3: + roundCarry = (noRestore ? (this.v[2] & roundCarryNoRestoreMask) + : (this.v[3] & roundCarryMask)) != 0; + z0 = this.v[3] >>> bitShiftsInWord; + break; + case 2: + roundCarry = (noRestore ? (this.v[1] & roundCarryNoRestoreMask) + : (this.v[2] & roundCarryMask)) != 0; + z1 = this.v[3] >>> bitShiftsInWord; + z0 = (noRestore ? 0 : this.v[3] << shiftRestore) + | (this.v[2] >>> bitShiftsInWord); + break; + case 1: + roundCarry = (noRestore ? (this.v[0] & roundCarryNoRestoreMask) + : (this.v[1] & roundCarryMask)) != 0; + z2 = this.v[3] >>> bitShiftsInWord; + z1 = (noRestore ? 0 : this.v[3] << shiftRestore) + | (this.v[2] >>> bitShiftsInWord); + z0 = (noRestore ? 0 : this.v[2] << shiftRestore) + | (this.v[1] >>> bitShiftsInWord); + break; + case 0: + roundCarry = (noRestore ? 0 : (this.v[0] & roundCarryMask)) != 0; + z3 = this.v[3] >>> bitShiftsInWord; + z2 = (noRestore ? 0 : this.v[3] << shiftRestore) + | (this.v[2] >>> bitShiftsInWord); + z1 = (noRestore ? 0 : this.v[2] << shiftRestore) + | (this.v[1] >>> bitShiftsInWord); + z0 = (noRestore ? 0 : this.v[1] << shiftRestore) + | (this.v[0] >>> bitShiftsInWord); + break; + default: + assert (false); + throw new RuntimeException(); + } + + update(z0, z1, z2, z3); + + // round up + if (roundUp && roundCarry) { + incrementDestructive(); + } + } + + private void shiftLeftDestructive(int wordShifts, int bitShiftsInWord) { + if (wordShifts == 0 && bitShiftsInWord == 0) { + return; + } + assert (wordShifts >= 0); + assert (bitShiftsInWord >= 0); + assert (bitShiftsInWord < 32); + if (wordShifts >= 4) { + zeroClear(); + return; + } + + final int shiftRestore = 32 - bitShiftsInWord; + // check this because "123 << 32" will be 123. + final boolean noRestore = bitShiftsInWord == 0; + int z0 = 0, z1 = 0, z2 = 0, z3 = 0; + + switch (wordShifts) { + case 3: + z3 = this.v[0] << bitShiftsInWord; + break; + case 2: + z2 = (this.v[0] << bitShiftsInWord); + z3 = (noRestore ? 0 : this.v[0] >>> shiftRestore) + | this.v[1] << bitShiftsInWord; + break; + case 1: + z1 = (this.v[0] << bitShiftsInWord); + z2 = (noRestore ? 0 : this.v[0] >>> shiftRestore) + | (this.v[1] << bitShiftsInWord); + z3 = (noRestore ? 0 : this.v[1] >>> shiftRestore) + | this.v[2] << bitShiftsInWord; + break; + case 0: + z0 = (this.v[0] << bitShiftsInWord); + z1 = (noRestore ? 0 : this.v[0] >>> shiftRestore) + | (this.v[1] << bitShiftsInWord); + z2 = (noRestore ? 0 : this.v[1] >>> shiftRestore) + | (this.v[2] << bitShiftsInWord); + z3 = (noRestore ? 0 : this.v[2] >>> shiftRestore) + | this.v[3] << bitShiftsInWord; + break; + default: + assert (false); + } + + update(z0, z1, z2, z3); + } + + /** + * Multiplies this value with the given value. + * + * @param left + * the value to multiply. in AND out. + * @param right + * the value to multiply. in + */ + private static void multiplyArrays4And4To4NoOverflow(int[] left, int[] right) { + assert (left.length == 4); + assert (right.length == 4); + long product; + + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK); + int z0 = (int) product; + + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[1] & SqlMathUtil.LONG_MASK) + + (right[1] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32); + int z1 = (int) product; + + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[2] & SqlMathUtil.LONG_MASK) + + (right[1] & SqlMathUtil.LONG_MASK) + * (left[1] & SqlMathUtil.LONG_MASK) + + (right[2] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32); + int z2 = (int) product; + + // v[3] + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[3] & SqlMathUtil.LONG_MASK) + + (right[1] & SqlMathUtil.LONG_MASK) + * (left[2] & SqlMathUtil.LONG_MASK) + + (right[2] & SqlMathUtil.LONG_MASK) + * (left[1] & SqlMathUtil.LONG_MASK) + + (right[3] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32); + int z3 = (int) product; + if ((product >>> 32) != 0) { + SqlMathUtil.throwOverflowException(); + } + + // the combinations below definitely result in overflow + if ((right[3] != 0 && (left[3] != 0 || left[2] != 0 || left[1] != 0)) + || (right[2] != 0 && (left[3] != 0 || left[2] != 0)) + || (right[1] != 0 && left[3] != 0)) { + SqlMathUtil.throwOverflowException(); + } + + left[0] = z0; + left[1] = z1; + left[2] = z2; + left[3] = z3; + } + + private static int[] multiplyArrays4And4To8(int[] left, int[] right) { + assert (left.length == 4); + assert (right.length == 4); + long product; + + // this method could go beyond the integer ranges until we scale back + // so, we need twice more variables. + int[] z = new int[8]; + + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK); + z[0] = (int) product; + + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[1] & SqlMathUtil.LONG_MASK) + + (right[1] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32); + z[1] = (int) product; + + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[2] & SqlMathUtil.LONG_MASK) + + (right[1] & SqlMathUtil.LONG_MASK) + * (left[1] & SqlMathUtil.LONG_MASK) + + (right[2] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32); + z[2] = (int) product; + + product = (right[0] & SqlMathUtil.LONG_MASK) + * (left[3] & SqlMathUtil.LONG_MASK) + + (right[1] & SqlMathUtil.LONG_MASK) + * (left[2] & SqlMathUtil.LONG_MASK) + + (right[2] & SqlMathUtil.LONG_MASK) + * (left[1] & SqlMathUtil.LONG_MASK) + + (right[3] & SqlMathUtil.LONG_MASK) + * (left[0] & SqlMathUtil.LONG_MASK) + (product >>> 32); + z[3] = (int) product; + + product = (right[1] & SqlMathUtil.LONG_MASK) + * (left[3] & SqlMathUtil.LONG_MASK) + + (right[2] & SqlMathUtil.LONG_MASK) + * (left[2] & SqlMathUtil.LONG_MASK) + + (right[3] & SqlMathUtil.LONG_MASK) + * (left[1] & SqlMathUtil.LONG_MASK) + (product >>> 32); + z[4] = (int) product; + + product = (right[2] & SqlMathUtil.LONG_MASK) + * (left[3] & SqlMathUtil.LONG_MASK) + + (right[3] & SqlMathUtil.LONG_MASK) + * (left[2] & SqlMathUtil.LONG_MASK) + (product >>> 32); + z[5] = (int) product; + + // v[1], v[0] + product = (right[3] & SqlMathUtil.LONG_MASK) + * (left[3] & SqlMathUtil.LONG_MASK) + (product >>> 32); + z[6] = (int) product; + z[7] = (int) (product >>> 32); + + return z; + } + + private static void incrementArray(int[] array) { + for (int i = 0; i < INT_COUNT; ++i) { + if (array[i] != SqlMathUtil.FULLBITS_32) { + array[i] = (int) ((array[i] & SqlMathUtil.LONG_MASK) + 1L); + break; + } + array[i] = 0; + if (i == INT_COUNT - 1) { + SqlMathUtil.throwOverflowException(); + } + } + } + + private static void decrementArray(int[] array) { + for (int i = 0; i < INT_COUNT; ++i) { + if (array[i] != 0) { + array[i] = (int) ((array[i] & SqlMathUtil.LONG_MASK) - 1L); + break; + } + array[i] = SqlMathUtil.FULLBITS_32; + if (i == INT_COUNT - 1) { + SqlMathUtil.throwOverflowException(); + } + } + } + + /** common implementation of difference. */ + private static byte differenceInternal(UnsignedInt128 left, int[] r, + UnsignedInt128 result) { + int cmp = left.compareTo(r); + if (cmp == 0) { + result.zeroClear(); + return 0; + } + + long sum = 0L; + if (cmp > 0) { + + // left is larger + for (int i = 0; i < INT_COUNT; ++i) { + sum = (left.v[i] & SqlMathUtil.LONG_MASK) + - (r[i] & SqlMathUtil.LONG_MASK) - ((int) -(sum >> 32)); + result.v[i] = (int) sum; + } + } else { + + // right is larger + for (int i = 0; i < INT_COUNT; ++i) { + sum = (r[i] & SqlMathUtil.LONG_MASK) + - (left.v[i] & SqlMathUtil.LONG_MASK) - ((int) -(sum >> 32)); + result.v[i] = (int) sum; + } + } + + if ((sum >> 32) != 0) { + SqlMathUtil.throwOverflowException(); + } + + return cmp > 0 ? (byte) 1 : (byte) -1; + } + + /** + * @see #compareTo(UnsignedInt128) + */ + private static int compareTo(int l0, int l1, int l2, int l3, int r0, int r1, + int r2, int r3) { + if (l3 != r3) { + return SqlMathUtil.compareUnsignedInt(l3, r3); + } + if (l2 != r2) { + return SqlMathUtil.compareUnsignedInt(l2, r2); + } + if (l1 != r1) { + return SqlMathUtil.compareUnsignedInt(l1, r1); + } + if (l0 != r0) { + return SqlMathUtil.compareUnsignedInt(l0, r0); + } + return 0; + } + + /** + * @param array + * @param tenScale + * @return Whether it overflowed + */ + private static boolean scaleUpTenArray(int[] array, short tenScale) { + while (tenScale > 0) { + long sum = 0L; + int powerTen = Math.min(tenScale, SqlMathUtil.MAX_POWER_TEN_INT31); + tenScale -= powerTen; + + final long rightUnsigned = SqlMathUtil.POWER_TENS_INT31[powerTen] + & SqlMathUtil.LONG_MASK; + for (int i = 0; i < INT_COUNT; ++i) { + sum = (array[i] & SqlMathUtil.LONG_MASK) * rightUnsigned + (sum >>> 32); + array[i] = (int) sum; + } + + if ((sum >> 32) != 0) { + + // overflow means this is smaller + return true; + } + } + return false; + } + + /** + * Scales down the given array (4 elements) for 10**tenScale. This method is + * only used from add/subtract/compare, and most likely with smaller tenScale. + * So, this method is not as optimized as + * {@link #scaleDownTenArray8RoundUp(int[], short)}. + * + * @param array + * array to scale down. in AND out. length must be 4. + * @param tenScale + * distance to scale down + */ + private static void scaleDownTenArray4RoundUp(int[] array, short tenScale) { + scaleDownFiveArray(array, tenScale); + shiftRightArray(tenScale, array, array, true); + } + + /** + * Scales down the given array (8 elements) for 10**tenScale. This method is + * frequently called from multiply(), so highly optimized. It's lengthy, but + * this is inevitable to avoid array/object creation.

Summary of this + * method

+ *

+ * This method employs division by inverse multiplication (except easy cases). + * because all inverses of powers of tens are pre-calculated, this is much + * faster than doing divisions. The matrix multiplication benchmark hit 3x + * performance with this. because the inverse is a little bit smaller than + * real value, the result is same or smaller than accurate answer, not larger. + * we take care of it after the first multiplication. + *

+ *

+ * Then, multiply it with power of tens (which is accurate) to correct +1 + * error and rounding up. let D = array - z * power: if D >= power/2, then + * ++z. Otherwise, do nothing. + *

+ * + * @param array + * array to scale down. in AND out. length must be 8. + * @param tenScale + * distance to scale down + */ + private static void scaleDownTenArray8RoundUp(int[] array, short tenScale) { + assert (array.length == 8); + if (tenScale > MAX_DIGITS) { + + // then definitely this will end up zero. + Arrays.fill(array, 0); + return; + } + if (tenScale <= SqlMathUtil.MAX_POWER_TEN_INT31) { + int divisor = SqlMathUtil.POWER_TENS_INT31[tenScale]; + assert (divisor > 0); + boolean round = divideCheckRound(array, divisor); + if (round) { + incrementArray(array); + } + return; + } + + // division by inverse multiplication. + final int[] inverse = SqlMathUtil.INVERSE_POWER_TENS_INT128[tenScale].v; + final int inverseWordShift = SqlMathUtil.INVERSE_POWER_TENS_INT128_WORD_SHIFTS[tenScale]; + assert (inverseWordShift <= 3); + assert (inverse[3] != 0); + for (int i = 5 + inverseWordShift; i < 8; ++i) { + if (array[i] != 0) { + SqlMathUtil.throwOverflowException(); // because inverse[3] is + // not zero + } + } + + int z4 = 0, z5 = 0, z6 = 0, z7 = 0; // because inverse is scaled 2^128, + // these will become v0-v3 + int z8 = 0, z9 = 0, z10 = 0; // for wordshift + long product = 0L; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[3] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[2] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[1] & SqlMathUtil.LONG_MASK); + z4 = (int) product; + product >>>= 32; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[3] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[2] & SqlMathUtil.LONG_MASK); + z5 = (int) product; + product >>>= 32; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[3] & SqlMathUtil.LONG_MASK); + z6 = (int) product; + product >>>= 32; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK); + z7 = (int) product; + product >>>= 32; + + if (inverseWordShift >= 1) { + product += (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK); + z8 = (int) product; + product >>>= 32; + + if (inverseWordShift >= 2) { + product += (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK); + z9 = (int) product; + product >>>= 32; + + if (inverseWordShift >= 3) { + product += (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK); + z10 = (int) product; + product >>>= 32; + } + } + } + + if (product != 0) { + SqlMathUtil.throwOverflowException(); + } + + // if inverse is word-shifted for accuracy, shift it back here. + switch (inverseWordShift) { + case 1: + z4 = z5; + z5 = z6; + z6 = z7; + z7 = z8; + break; + case 2: + z4 = z6; + z5 = z7; + z6 = z8; + z7 = z9; + break; + case 3: + z4 = z7; + z5 = z8; + z6 = z9; + z7 = z10; + break; + default: + break; + } + + // now, correct +1 error and rounding up. + final int[] power = SqlMathUtil.POWER_TENS_INT128[tenScale].v; + final int[] half = SqlMathUtil.ROUND_POWER_TENS_INT128[tenScale].v; + + int d0, d1, d2, d3, d4; + product = (array[0] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK); + d0 = (int) product; + + product = (array[1] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK) + - ((int) -(product >> 32)); + d1 = (int) product; + + product = (array[2] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) * (z6 & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK) + - (power[2] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK) + - ((int) -(product >> 32)); + d2 = (int) product; + + product = (array[3] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) * (z7 & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) * (z6 & SqlMathUtil.LONG_MASK) + - (power[2] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK) + - (power[3] & SqlMathUtil.LONG_MASK) * (z4 & SqlMathUtil.LONG_MASK) + - ((int) -(product >> 32)); + d3 = (int) product; + + product = (array[4] & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) * (z7 & SqlMathUtil.LONG_MASK) + - (power[2] & SqlMathUtil.LONG_MASK) * (z6 & SqlMathUtil.LONG_MASK) + - (power[3] & SqlMathUtil.LONG_MASK) * (z5 & SqlMathUtil.LONG_MASK) + - ((int) -(product >> 32)); + d4 = (int) product; + + // If the difference is larger than 2^128 (d4 != 0), then D is + // definitely larger than power, so increment. + // otherwise, compare it with power and half. + boolean increment = (d4 != 0) + || (compareTo(d0, d1, d2, d3, half[0], half[1], half[2], half[3]) >= 0); + + array[0] = z4; + array[1] = z5; + array[2] = z6; + array[3] = z7; + if (increment) { + incrementArray(array); + } + } + + /** + * Scales down the given array for 5**fiveScale. + * + * @param array + * array to scale down + * @param fiveScale + * distance to scale down + * @return Whether it requires incrementing if rounding + */ + private static boolean scaleDownFiveArray(int[] array, short fiveScale) { + while (true) { + int powerFive = Math.min(fiveScale, SqlMathUtil.MAX_POWER_FIVE_INT31); + fiveScale -= powerFive; + + int divisor = SqlMathUtil.POWER_FIVES_INT31[powerFive]; + assert (divisor > 0); + if (fiveScale == 0) { + return divideCheckRound(array, divisor); + } else { + divideCheckRound(array, divisor); + } + } + } + + private static boolean divideCheckRound(int[] array, int divisor) { + long remainder = 0; + for (int i = array.length - 1; i >= 0; --i) { + remainder = ((array[i] & SqlMathUtil.LONG_MASK) + (remainder << 32)); + array[i] = (int) (remainder / divisor); + remainder %= divisor; + } + + return (remainder >= (divisor >> 1)); + } + + private static void scaleDownFiveArrayRoundUp(int[] array, short tenScale) { + boolean rounding = scaleDownFiveArray(array, tenScale); + if (rounding) { + incrementArray(array); + } + } + + /** + * Internal method to apply the result of multiplication with right-shifting. + * This method does round the value while right-shifting (SQL Numeric + * semantics). + * + * @param rightShifts + * distance of right-shifts + */ + private static void shiftRightArray(int rightShifts, int[] z, int[] result, + boolean round) { + assert (rightShifts >= 0); + if (rightShifts == 0) { + for (int i = 0; i < INT_COUNT; ++i) { + if (z[i + INT_COUNT] != 0) { + SqlMathUtil.throwOverflowException(); + } + } + result[0] = z[0]; + result[1] = z[1]; + result[2] = z[2]; + result[3] = z[3]; + } else { + final int wordShifts = rightShifts / 32; + final int bitShiftsInWord = rightShifts % 32; + final int shiftRestore = 32 - bitShiftsInWord; + + // check this because "123 << 32" will be 123. + final boolean noRestore = bitShiftsInWord == 0; + + // overflow checks + if (z.length > INT_COUNT) { + if (wordShifts + INT_COUNT < z.length + && (z[wordShifts + INT_COUNT] >>> bitShiftsInWord) != 0) { + SqlMathUtil.throwOverflowException(); + } + + for (int i = 1; i < INT_COUNT; ++i) { + if (i + wordShifts < z.length - INT_COUNT + && z[i + wordShifts + INT_COUNT] != 0) { + SqlMathUtil.throwOverflowException(); + } + } + } + + // check round-ups before settings values to result. + // be aware that result could be the same object as z. + boolean roundCarry = false; + if (round) { + if (bitShiftsInWord == 0) { + assert (wordShifts > 0); + roundCarry = z[wordShifts - 1] < 0; + } else { + roundCarry = (z[wordShifts] & (1 << (bitShiftsInWord - 1))) != 0; + } + } + + // extract the values. + for (int i = 0; i < INT_COUNT; ++i) { + int val = 0; + if (!noRestore && i + wordShifts + 1 < z.length) { + val = z[i + wordShifts + 1] << shiftRestore; + } + if (i + wordShifts < z.length) { + val |= (z[i + wordShifts] >>> bitShiftsInWord); + } + result[i] = val; + } + + if (roundCarry) { + incrementArray(result); + } + } + } + + /** + * helper method for multiplication. used when either left/right fits int32. + */ + private void multiplyDestructiveFitsInt32(UnsignedInt128 right, + short rightShifts, short tenScaleDown) { + assert (this.fitsInt32() && right.fitsInt32()); + assert (rightShifts == 0 || tenScaleDown == 0); // only one of them + if (this.isZero()) { + return; // zero. no need to shift/scale + } else if (right.isZero()) { + zeroClear(); + return; // zero. no need to shift/scale + } else if (this.isOne()) { + this.update(right); + } else { + this.multiplyDestructive(right.v[0]); + } + + if (rightShifts > 0) { + this.shiftRightDestructive(rightShifts, true); + } else if (tenScaleDown > 0) { + this.scaleDownTenDestructive(tenScaleDown); + } + } + + /** Updates the value of {@link #cnt} by checking {@link #v}. */ + private void updateCount() { + if (v[3] != 0) { + this.count = (byte) 4; + } else if (v[2] != 0) { + this.count = (byte) 3; + } else if (v[1] != 0) { + this.count = (byte) 2; + } else if (v[0] != 0) { + this.count = (byte) 1; + } else { + this.count = (byte) 0; + } + } +} diff --git a/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java b/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java new file mode 100644 index 0000000..3c05352 --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java @@ -0,0 +1,414 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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 static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.apache.hadoop.hive.common.type.UnsignedInt128; + +/** + * This code was originally written for Microsoft PolyBase. + */ + +public class TestDecimal128 { + private Decimal128 zero; + + private Decimal128 one; + + private Decimal128 two; + + @Before + public void setUp() throws Exception { + zero = new Decimal128(0); + one = new Decimal128(1); + two = new Decimal128(2); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testCalculateTenThirtyEight() { + Decimal128 ten = new Decimal128(10, (short) 0); + Decimal128 val = new Decimal128(1, (short) 0); + for (int i = 0; i < 38; ++i) { + val.multiplyDestructive(ten, (short) 0); + } + } + + @Test + public void testHashCode() { + assertTrue(one.hashCode() != two.hashCode()); + assertTrue(zero.hashCode() != one.hashCode()); + assertTrue(zero.hashCode() != two.hashCode()); + + assertEquals(zero.hashCode(), new Decimal128(0).hashCode()); + assertEquals(one.hashCode(), new Decimal128(1).hashCode()); + assertEquals(two.hashCode(), new Decimal128(2).hashCode()); + + // scaled value might be not equal, but after scaling it should. + Decimal128 oneScaled = new Decimal128(1L, (short) 3); + oneScaled.changeScaleDestructive((short) 0); + assertEquals(one.hashCode(), oneScaled.hashCode()); + } + + @Test + public void testEquals() { + assertTrue(!one.equals(two)); + assertTrue(!zero.equals(one)); + assertTrue(!zero.equals(two)); + + assertEquals(zero, new Decimal128(0)); + assertEquals(one, new Decimal128(1)); + assertEquals(two, new Decimal128(2)); + + // scaled value might be not equal, but after scaling it should. + Decimal128 oneScaled = new Decimal128(1L, (short) 3); + oneScaled.changeScaleDestructive((short) 0); + assertEquals(one, oneScaled); + } + + @Test + public void testCompareTo() { + assertTrue(one.compareTo(two) < 0); + assertTrue(two.compareTo(one) > 0); + assertTrue(one.compareTo(zero) > 0); + assertTrue(zero.compareTo(two) < 0); + + // compare to must compare with scaling up/down. + Decimal128 oneScaled = new Decimal128(1L, (short) 3); + assertTrue(one.compareTo(oneScaled) == 0); + + // exact numbers (power of 2) can do the same + Decimal128 d1 = new Decimal128(2.0d, (short) 6); + Decimal128 d2 = new Decimal128(2.0d, (short) 3); + assertTrue(d1.compareTo(d2) == 0); + + // but, if the value is rounded by more scaling, + // they will be different values. + Decimal128 d3 = new Decimal128(2.0d / 3.0d, (short) 5); + Decimal128 d4 = new Decimal128(2.0d / 3.0d, (short) 8); + assertTrue(d3.compareTo(d4) != 0); + } + + @Test + public void testText() { + assertEquals("1", one.toFormalString()); + assertEquals(0, new Decimal128("1", (short) 0).compareTo(one)); + assertEquals("2", two.toFormalString()); + assertEquals(0, new Decimal128("2", (short) 0).compareTo(two)); + assertEquals("0", zero.toFormalString()); + assertEquals(0, new Decimal128("0", (short) 0).compareTo(zero)); + + assertEquals("1.000", new Decimal128(1L, (short) 3).toFormalString()); + assertEquals(0, new Decimal128("1", (short) 3).compareTo(one)); + + assertEquals("2.000000", new Decimal128(2.0d, (short) 6).toFormalString()); + assertEquals("2.000", new Decimal128(2.0d, (short) 3).toFormalString()); + assertEquals(0, new Decimal128("2.0", (short) 6).compareTo(two)); + assertEquals(0, new Decimal128("2.0", (short) 3).compareTo(two)); + + assertEquals("1.3330", new Decimal128("1.333", (short) 4).toFormalString()); + assertEquals("1.333000", + new Decimal128("1.333", (short) 6).toFormalString()); + assertEquals("1.333", new Decimal128("1.333", (short) 3).toFormalString()); + assertEquals("1.33", new Decimal128("1.333", (short) 2).toFormalString()); + assertEquals("1.33", new Decimal128("1.333", (short) 2).toFormalString()); + + assertEquals("0.13330", + new Decimal128("1333E-4", (short) 5).toFormalString()); + assertEquals("0.01333", + new Decimal128("1333E-5", (short) 5).toFormalString()); + assertEquals("13330000.00", + new Decimal128("1333E4", (short) 2).toFormalString()); + + assertEquals("123456789012345678901234.56789", new Decimal128( + "123456789012345678901234567.8901234E-3", (short) 5).toFormalString()); + } + + @Test + public void testAdd() { + Decimal128 result = new Decimal128(); + Decimal128.add(one, two, result, (short) 2); + + assertEquals(0, new Decimal128(3L, (short) 0).compareTo(result)); + + Decimal128.add(two, two, result, (short) 1); + + assertEquals(0, new Decimal128(4L, (short) 0).compareTo(result)); + + long l1 = 123456789012345L; + long l2 = 987654321097L; + long sum = l1 + l2; + Decimal128 left = new Decimal128(l1, (short) 3); + Decimal128 right = new Decimal128(l2, (short) 5); + Decimal128.add(left, right, result, (short) 2); + assertEquals(0, new Decimal128(sum, (short) 0).compareTo(result)); + Decimal128.add(right, left, result, (short) 2); + assertEquals(0, new Decimal128(sum, (short) 0).compareTo(result)); + } + + @Test + public void testSubtract() { + Decimal128 result = new Decimal128(); + Decimal128.subtract(one, two, result, (short) 2); + assertEquals(0, new Decimal128(-1L, (short) 0).compareTo(result)); + + Decimal128.subtract(two, one, result, (short) 2); + assertEquals(0, new Decimal128(1L, (short) 0).compareTo(result)); + + Decimal128.subtract(two, two, result, (short) 1); + assertEquals(0, zero.compareTo(result)); + assertEquals(0, result.getSignum()); + + long l1 = 123456789012345L; + long l2 = 987654321097L; + long sub = l1 - l2; + Decimal128 left = new Decimal128(l1, (short) 3); + Decimal128 right = new Decimal128(l2, (short) 5); + Decimal128.subtract(left, right, result, (short) 2); + assertEquals(0, new Decimal128(sub, (short) 0).compareTo(result)); + Decimal128.subtract(right, left, result, (short) 2); + assertEquals(0, new Decimal128(-sub, (short) 0).compareTo(result)); + + Decimal128 val = new Decimal128("1.123", (short) 3); + val.addDestructive(new Decimal128("4.321", (short) 3), (short) 3); + assertEquals("5.444", val.toFormalString()); + } + + @Test + public void testMultiply() { + Decimal128 result = new Decimal128(); + Decimal128.multiply(one, two, result, (short) 2); + assertEquals(0, two.compareTo(result)); + + Decimal128.multiply(two, two, result, (short) 2); + assertEquals(0, new Decimal128(4L, (short) 0).compareTo(result)); + + long l1 = 123456789012345L; + long l2 = 987654321097L; + Decimal128 left = new Decimal128(l1, (short) 0); + Decimal128 right = new Decimal128(l2, (short) 0); + UnsignedInt128 unscaled = new UnsignedInt128(l1) + .multiplyConstructive(new UnsignedInt128(l2)); + Decimal128 ans = new Decimal128(unscaled, (short) 0, false); + Decimal128.multiply(left, right, result, (short) 0); + assertEquals(0, ans.compareTo(result)); + Decimal128.multiply(right, left, result, (short) 0); + assertEquals(0, ans.compareTo(result)); + + Decimal128.multiply(new Decimal128(1.123d, (short) 10), new Decimal128( + 4.321d, (short) 10), result, (short) 10); + assertEquals(1.123d * 4.321d, result.doubleValue(), 0.00001d); + + // because only 10 fractional digits, it's not this much accurate + assertNotEquals(1.123d * 4.321d, result.doubleValue(), 0.00000000000000001d); + + Decimal128.multiply(new Decimal128(1.123d, (short) 2), new Decimal128( + 4.321d, (short) 2), result, (short) 2); + + // this time even more inaccurate + assertEquals(1.123d * 4.321d, result.doubleValue(), 1.0d); + assertNotEquals(1.123d * 4.321d, result.doubleValue(), 0.000001d); + + Decimal128 val = new Decimal128("1.123", (short) 3); + val.multiplyDestructive(new Decimal128("4.321", (short) 3), (short) 6); + assertEquals("4.852483", val.toFormalString()); + + Decimal128 val1 = new Decimal128("1.0001", (short) 4); + val1.multiplyDestructive(new Decimal128("1.0001", (short) 4), (short) 8); + assertEquals("1.00020001", val1.toFormalString()); + + } + + // Assert that a and b are not the same, within epsilon tolerance. + private void assertNotEquals(double a, double b, double epsilon) { + assertTrue(Math.abs(a - b) > epsilon); + } + + @Test + public void testDivide() { + Decimal128 quotient = new Decimal128(); + Decimal128 remainder = new Decimal128(); + Decimal128.divide(two, one, quotient, remainder, (short) 2); + assertEquals(0, quotient.compareTo(two)); + assertTrue(remainder.isZero()); + + Decimal128.divide(two, two, quotient, remainder, (short) 2); + assertEquals(0, quotient.compareTo(one)); + assertTrue(remainder.isZero()); + + Decimal128 three = new Decimal128(3); + Decimal128 four = new Decimal128(4); + Decimal128.divide(three, four, quotient, remainder, (short) 2); + assertEquals("0.75", quotient.toFormalString()); + assertEquals("0", remainder.toFormalString()); + + Decimal128.divide(three, four, quotient, remainder, (short) 1); + assertEquals("0.7", quotient.toFormalString()); + assertEquals("0.2", remainder.toFormalString()); + + Decimal128.divide(three, four, quotient, remainder, (short) 0); + assertEquals("0", quotient.toFormalString()); + assertEquals("3", remainder.toFormalString()); + } + + @Test + public void testPiNewton() { + + // see http://en.wikipedia.org/wiki/Approximations_of_%CF%80 + // Below is the simple Newton's equation + final int LOOPS = 100; + final short SCALE = 33; + Decimal128 current = new Decimal128(1, SCALE); + Decimal128 multiplier = new Decimal128(); + Decimal128 dividor = new Decimal128(); + Decimal128 remainder = new Decimal128(); + Decimal128 one = new Decimal128(1); + for (int i = LOOPS; i > 0; --i) { + multiplier.update(i, SCALE); + current.multiplyDestructive(multiplier, SCALE); + dividor.update(1 + 2 * i, SCALE); + current.divideDestructive(dividor, SCALE, remainder); + current.addDestructive(one, SCALE); + } + current.multiplyDestructive(new Decimal128(2), SCALE); + assertTrue(current.toFormalString().startsWith("3.141592653589793238")); + } + + @Test + public void testPiArcsine() { + + // This one uses the arcsin method. Involves more multiplications/divisions. + // pi=Sum (3 * 2n!/(16^n * (2n+1) * n! * n!)) + // =Sum (3 * ((n+1)(n+2)...2n)/n!*16^n/(2n+1)) + // =Sum (3 / (2n+1) * (n+1)/16 * (n+2)/32... * 2n/16(n+1)) + // (note that it is split so that each term is not overflown) + final int LOOPS = 50; + final short SCALE = 30; + Decimal128 total = new Decimal128(0); + Decimal128 multiplier = new Decimal128(); + Decimal128 dividor = new Decimal128(); + Decimal128 remainder = new Decimal128(); + Decimal128 current = new Decimal128(); + for (int i = 0; i < LOOPS; ++i) { + current.update(3, SCALE); + dividor.update(2 * i + 1, SCALE); + current.divideDestructive(dividor, SCALE, remainder); + for (int j = 1; j <= i; ++j) { + multiplier.update(i + j, SCALE); + dividor.update(16 * j, SCALE); + current.multiplyDestructive(multiplier, SCALE); + current.divideDestructive(dividor, SCALE, remainder); + } + + total.addDestructive(current, SCALE); + } + + assertTrue(total.toFormalString().startsWith("3.141592653589793238462")); + } + + @Test + public void testDoubleValue() { + Decimal128 quotient = new Decimal128(); + Decimal128 remainder = new Decimal128(); + + Decimal128 three = new Decimal128(3); + Decimal128 four = new Decimal128(9); + Decimal128.divide(three, four, quotient, remainder, (short) 38); + assertEquals(0.33333333333333333333333333d, quotient.doubleValue(), + 0.0000000000000000000000001d); + + Decimal128 minusThree = new Decimal128(-3); + Decimal128.divide(minusThree, four, quotient, remainder, (short) 38); + assertEquals(-0.33333333333333333333333333d, quotient.doubleValue(), + 0.0000000000000000000000001d); + } + + @Test + public void testFloatValue() { + Decimal128 quotient = new Decimal128(); + Decimal128 remainder = new Decimal128(); + + Decimal128 three = new Decimal128(3); + Decimal128 four = new Decimal128(9); + Decimal128.divide(three, four, quotient, remainder, (short) 38); + assertEquals(0.3333333333333333f, quotient.floatValue(), 0.00000000001f); + + Decimal128 minusThree = new Decimal128(-3); + Decimal128.divide(minusThree, four, quotient, remainder, (short) 38); + assertEquals(-0.333333333333333f, quotient.floatValue(), 0.00000000001f); + } + + @Test + public void testSqrtAsDouble() { + Decimal128 val1 = new Decimal128("1.00435134913958923485982394892384", + (short) 36); + Decimal128 val2 = new Decimal128("1.00345982739817298323423423", (short) 36); + assertEquals(1.00217331292526d, val1.sqrtAsDouble(), 0.000000000000001d); + assertEquals(1.00172841998127d, val2.sqrtAsDouble(), 0.000000000000001d); + + } + + @Test + public void testPowAsDouble() { + Decimal128 val1 = new Decimal128("1.00435134913958923485982394892384", + (short) 36); + assertEquals(1.004366436877081d, + val1.powAsDouble(1.00345982739817298323423423d), 0.000000000000001d); + + Decimal128 val2 = new Decimal128("1.001", (short) 36); + assertEquals(1.0100451202102512d, val2.powAsDouble(10), 0.000000000000001d); + } + + @Test + public void testPrecisionOverflow() { + new Decimal128("1.004", (short) 3).checkPrecisionOverflow(4); + + try { + new Decimal128("1.004", (short) 3).checkPrecisionOverflow(3); + fail(); + } catch (ArithmeticException ex) { + } + + try { + new Decimal128("1.004", (short) 3).checkPrecisionOverflow(2); + fail(); + } catch (ArithmeticException ex) { + } + + new Decimal128("1.004", (short) 3).checkPrecisionOverflow(38); + + new Decimal128("-3322", (short) 0).checkPrecisionOverflow(4); + try { + new Decimal128("-3322", (short) 0).checkPrecisionOverflow(3); + fail(); + } catch (ArithmeticException ex) { + } + + new Decimal128("-3322", (short) 1).checkPrecisionOverflow(5); + try { + new Decimal128("-3322", (short) 1).checkPrecisionOverflow(4); + fail(); + } catch (ArithmeticException ex) { + } + } +} diff --git a/common/src/test/org/apache/hadoop/hive/common/type/TestSignedInt128.java b/common/src/test/org/apache/hadoop/hive/common/type/TestSignedInt128.java new file mode 100644 index 0000000..e447ace --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestSignedInt128.java @@ -0,0 +1,446 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.math.BigInteger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Testcases for {@link SignedInt128} + * + * This code was originally written for Microsoft PolyBase. + */ +public class TestSignedInt128 { + private SignedInt128 zero; + + private SignedInt128 one; + + private SignedInt128 two; + + private SignedInt128 negativeOne; + + private SignedInt128 negativeTwo; + + @Before + public void setUp() throws Exception { + zero = new SignedInt128(0); + one = new SignedInt128(1); + two = new SignedInt128(2); + negativeOne = new SignedInt128(-1); + negativeTwo = new SignedInt128(-2); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testHashCode() { + assertTrue(one.hashCode() != two.hashCode()); + assertTrue(zero.hashCode() != one.hashCode()); + assertTrue(zero.hashCode() != two.hashCode()); + + assertTrue(one.hashCode() != negativeOne.hashCode()); + assertTrue(two.hashCode() != negativeTwo.hashCode()); + assertEquals(zero.hashCode(), new SignedInt128(-0).hashCode()); + + assertEquals(zero.hashCode(), new SignedInt128(0).hashCode()); + assertEquals(one.hashCode(), new SignedInt128(1).hashCode()); + assertEquals(two.hashCode(), new SignedInt128(2).hashCode()); + } + + @Test + public void testEquals() { + assertTrue(!one.equals(two)); + assertTrue(!zero.equals(one)); + assertTrue(!zero.equals(two)); + + assertEquals(zero, new SignedInt128(0)); + assertEquals(one, new SignedInt128(1)); + assertEquals(two, new SignedInt128(2)); + + assertTrue(!one.equals(negativeOne)); + assertTrue(!two.equals(negativeTwo)); + assertEquals(zero, new SignedInt128(-0)); + } + + @Test + public void testCompareTo() { + assertTrue(one.compareTo(two) < 0); + assertTrue(two.compareTo(one) > 0); + assertTrue(one.compareTo(zero) > 0); + assertTrue(zero.compareTo(two) < 0); + + assertTrue(zero.compareTo(negativeOne) > 0); + assertTrue(zero.compareTo(negativeTwo) > 0); + assertTrue(one.compareTo(negativeOne) > 0); + assertTrue(one.compareTo(negativeTwo) > 0); + assertTrue(two.compareTo(negativeOne) > 0); + assertTrue(two.compareTo(negativeTwo) > 0); + assertTrue(negativeOne.compareTo(negativeTwo) > 0); + assertTrue(negativeTwo.compareTo(negativeOne) < 0); + } + + @Test + public void testToFormalString() { + assertEquals("0", zero.toFormalString()); + assertEquals("1", one.toFormalString()); + assertEquals("-1", negativeOne.toFormalString()); + assertEquals("-2", negativeTwo.toFormalString()); + + assertEquals("30", new SignedInt128(30).toFormalString()); + assertEquals("680000000000", + new SignedInt128(680000000000L).toFormalString()); + assertEquals("6800000000000", + new SignedInt128(6800000000000L).toFormalString()); + assertEquals("68", new SignedInt128(68).toFormalString()); + + assertEquals("-30", new SignedInt128(-30).toFormalString()); + assertEquals("-680000000000", + new SignedInt128(-680000000000L).toFormalString()); + assertEquals("-6800000000000", + new SignedInt128(-6800000000000L).toFormalString()); + assertEquals("-68", new SignedInt128(-68).toFormalString()); + + assertEquals(zero, new SignedInt128("0")); + assertEquals(one, new SignedInt128("1")); + + assertEquals(zero, new SignedInt128("-0")); + assertEquals(negativeOne, new SignedInt128("-1")); + assertEquals(negativeTwo, new SignedInt128("-2")); + + assertEquals(new SignedInt128(30), new SignedInt128("30")); + assertEquals(new SignedInt128(680000000000L), new SignedInt128( + "680000000000")); + assertEquals(new SignedInt128(6800000000000L), new SignedInt128( + "6800000000000")); + assertEquals(new SignedInt128(68), new SignedInt128("68")); + + assertEquals(new SignedInt128(-30), new SignedInt128("-30")); + assertEquals(new SignedInt128(-680000000000L), new SignedInt128( + "-680000000000")); + assertEquals(new SignedInt128(-6800000000000L), new SignedInt128( + "-6800000000000")); + assertEquals(new SignedInt128(-68), new SignedInt128("-68")); + } + + @Test + public void testSignedInt128() { + assertEquals(0L, new SignedInt128().longValue()); + } + + @Test + public void testSignedInt128SignedInt128() { + assertEquals(1L, new SignedInt128(one).longValue()); + assertEquals(2L, new SignedInt128(two).longValue()); + } + + @Test + public void testSignedInt128IntIntIntInt() { + assertEquals(((long) 11) << 32L | 23L, + new SignedInt128(23, 11, 0, 0).longValue()); + } + + @Test + public void testZeroClear() { + assertFalse(one.isZero()); + assertFalse(two.isZero()); + assertTrue(0L != one.longValue()); + assertTrue(0L != two.longValue()); + + two.zeroClear(); + + assertTrue(0L != one.longValue()); + assertEquals(0L, two.longValue()); + assertFalse(one.isZero()); + assertTrue(two.isZero()); + + one.zeroClear(); + + assertEquals(0L, one.longValue()); + assertEquals(0L, two.longValue()); + assertTrue(one.isZero()); + assertTrue(two.isZero()); + } + + @Test + public void testAddDestructive() { + one.addDestructive(two); + assertEquals(3L, one.longValue()); + assertEquals(2L, two.longValue()); + + SignedInt128 big = new SignedInt128((1L << 62) + 3L); + SignedInt128 tmp = new SignedInt128(0L); + for (int i = 0; i < 54; ++i) { + tmp.addDestructive(big); + } + + assertEquals(3 * 54, tmp.getV0()); + assertEquals(0x80000000, tmp.getV1()); // (54 % 4) << 62 + assertEquals(13, tmp.getV2()); // 54/4 + assertEquals(0, tmp.getV3()); + + assertEquals((1L << 62) + 3L, big.longValue()); + + SignedInt128 huge = new SignedInt128(one); + huge.shiftLeftDestructive(125); + SignedInt128 huge2 = new SignedInt128(one); + huge2.shiftLeftDestructive(125); + try { + huge2.addDestructive(huge); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testSubtractDestructive() { + two.subtractDestructive(one); + assertEquals(1L, one.longValue()); + assertEquals(1L, one.longValue()); + + one.subtractDestructive(new SignedInt128(10L)); + assertEquals(-9L, one.longValue()); + + SignedInt128 big = new SignedInt128((1L << 62) + (3L << 34) + 3L); + big.shiftLeftDestructive(6); + SignedInt128 tmp = new SignedInt128((1L << 61) + 5L); + tmp.shiftLeftDestructive(6); + + big.subtractDestructive(tmp); + big.subtractDestructive(tmp); + + assertEquals((3 << 6) - 2 * (5 << 6), big.getV0()); + assertEquals((3 << 8) - 1, big.getV1()); + assertEquals(0, big.getV2()); + assertEquals(0, big.getV3()); + } + + @Test + public void testMultiplyDestructiveInt() { + two.multiplyDestructive(1); + assertEquals(2L, two.longValue()); + assertEquals(1L, one.longValue()); + two.multiplyDestructive(2); + assertEquals(4L, two.longValue()); + + SignedInt128 five = new SignedInt128(5); + five.multiplyDestructive(6432346); + assertEquals(6432346 * 5, five.getV0()); + assertEquals(0, five.getV1()); + assertEquals(0, five.getV2()); + assertEquals(0, five.getV3()); + + SignedInt128 big = new SignedInt128((1L << 62) + (3L << 34) + 3L); + big.multiplyDestructive(96); + + assertEquals(3 * 96, big.getV0()); + assertEquals(96 * (3 << 2), big.getV1()); + assertEquals(96 / 4, big.getV2()); + assertEquals(0, big.getV3()); + + SignedInt128 tmp = new SignedInt128(1); + tmp.shiftLeftDestructive(126); + try { + tmp.multiplyDestructive(2); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testShiftDestructive() { + SignedInt128 big = new SignedInt128((1L << 62) + (23L << 32) + 89L); + big.shiftLeftDestructive(2); + + assertEquals(89 * 4, big.getV0()); + assertEquals(23 * 4, big.getV1()); + assertEquals(1, big.getV2()); + assertEquals(0, big.getV3()); + + big.shiftLeftDestructive(32); + + assertEquals(0, big.getV0()); + assertEquals(89 * 4, big.getV1()); + assertEquals(23 * 4, big.getV2()); + assertEquals(1, big.getV3()); + + big.shiftRightDestructive(2, true); + + assertEquals(0, big.getV0()); + assertEquals(89, big.getV1()); + assertEquals(23 + (1 << 30), big.getV2()); + assertEquals(0, big.getV3()); + + big.shiftRightDestructive(32, true); + + assertEquals(89, big.getV0()); + assertEquals(23 + (1 << 30), big.getV1()); + assertEquals(0, big.getV2()); + assertEquals(0, big.getV3()); + + // test rounding + SignedInt128 tmp = new SignedInt128(17); + assertEquals(17, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(9, tmp.getV0()); + tmp.shiftRightDestructive(1, false); + assertEquals(4, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(2, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(1, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(1, tmp.getV0()); + tmp.shiftRightDestructive(1, false); + assertEquals(0, tmp.getV0()); + } + + @Test + public void testMultiplyDestructiveSignedInt128() { + two.multiplyDestructive(one); + assertEquals(2L, two.longValue()); + assertEquals(1L, one.longValue()); + two.multiplyDestructive(two); + assertEquals(4L, two.longValue()); + + SignedInt128 five = new SignedInt128(5); + five.multiplyDestructive(new SignedInt128(6432346)); + assertEquals(6432346 * 5, five.getV0()); + assertEquals(0, five.getV1()); + assertEquals(0, five.getV2()); + assertEquals(0, five.getV3()); + + SignedInt128 big = new SignedInt128((1L << 62) + (3L << 34) + 3L); + big.multiplyDestructive(new SignedInt128(96)); + + assertEquals(3 * 96, big.getV0()); + assertEquals(96 * (3 << 2), big.getV1()); + assertEquals(96 / 4, big.getV2()); + assertEquals(0, big.getV3()); + + SignedInt128 tmp = new SignedInt128(1); + tmp.shiftLeftDestructive(126); + try { + tmp.multiplyDestructive(new SignedInt128(2)); + fail(); + } catch (ArithmeticException ex) { + // ok + } + + SignedInt128 complicated1 = new SignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + SignedInt128 complicated2 = new SignedInt128(54234234, 9, 0, 0); + BigInteger bigInteger2 = complicated2.toBigIntegerSlow(); + complicated1.multiplyDestructive(complicated2); + BigInteger ans = bigInteger1.multiply(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + + try { + SignedInt128 complicated3 = new SignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0); + complicated3.multiplyDestructive(new SignedInt128(54234234, 9845, 0, 0)); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testDivideDestructiveInt() { + two.divideDestructive(1); + assertEquals(1L, one.longValue()); + assertEquals(2L, two.longValue()); + one.divideDestructive(2); + assertEquals(0L, one.longValue()); + assertEquals(2L, two.longValue()); + + SignedInt128 var1 = new SignedInt128(1234234662345L); + var1.divideDestructive(642337); + assertEquals(1234234662345L / 642337L, var1.longValue()); + + SignedInt128 complicated1 = new SignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + complicated1.divideDestructive(1534223465); + BigInteger bigInteger2 = BigInteger.valueOf(1534223465); + BigInteger ans = bigInteger1.divide(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + + try { + complicated1.divideDestructive(0); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testDivideDestructiveSignedInt128() { + SignedInt128 remainder = new SignedInt128(); + two.divideDestructive(one, remainder); + assertEquals(1L, one.longValue()); + assertEquals(2L, two.longValue()); + assertEquals(zero, remainder); + one.divideDestructive(two, remainder); + assertEquals(0L, one.longValue()); + assertEquals(2L, two.longValue()); + assertEquals(new SignedInt128(1), remainder); + + SignedInt128 var1 = new SignedInt128(1234234662345L); + var1.divideDestructive(new SignedInt128(642337), remainder); + assertEquals(1234234662345L / 642337L, var1.longValue()); + assertEquals(1234234662345L % 642337L, remainder.longValue()); + + SignedInt128 complicated1 = new SignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0x42395ADC); + SignedInt128 complicated2 = new SignedInt128(0xF09DC19A, 0x00001234, 0, 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + BigInteger bigInteger2 = complicated2.toBigIntegerSlow(); + complicated1.divideDestructive(complicated2, remainder); + BigInteger ans = bigInteger1.divide(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + + try { + complicated1.divideDestructive(zero, remainder); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testDivideDestructiveSignedInt128Again() { + SignedInt128 complicated1 = new SignedInt128(0xF9892FCA, 0x59D109AD, 0, 0); + SignedInt128 complicated2 = new SignedInt128(0xF09DC19A, 3, 0, 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + BigInteger bigInteger2 = complicated2.toBigIntegerSlow(); + complicated1.divideDestructive(complicated2, new SignedInt128()); + BigInteger ans = bigInteger1.divide(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + } +} diff --git a/common/src/test/org/apache/hadoop/hive/common/type/TestSqlMathUtil.java b/common/src/test/org/apache/hadoop/hive/common/type/TestSqlMathUtil.java new file mode 100644 index 0000000..18e0f7f --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestSqlMathUtil.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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 static org.junit.Assert.*; +import org.junit.Test; + +/** + * This code was originally written for Microsoft PolyBase. + */ +public class TestSqlMathUtil { + @Test + public void testDivision() { + { + int[] dividend = new int[] { 1 + 33, 2 + 21, 3, 4 + 10, 20, 30, 40, 0 }; + int[] divisor = new int[] { 1, 2, 3, 4 }; + int[] quotient = new int[5]; + int[] remainder = SqlMathUtil.divideMultiPrecision(dividend, divisor, + quotient); + assertArrayEquals(new int[] { 1, 0, 0, 10, 0 }, quotient); + assertArrayEquals(new int[] { 33, 21, 0, 0, 0, 0, 0, 0, 0 }, remainder); + } + + { + int[] dividend = new int[] { 0xF7000000, 0, 0x39000000, 0 }; + int[] divisor = new int[] { 0xF700, 0, 0x3900, 0 }; + int[] quotient = new int[5]; + int[] remainder = SqlMathUtil.divideMultiPrecision(dividend, divisor, + quotient); + assertArrayEquals(new int[] { 0x10000, 0, 0, 0, 0 }, quotient); + assertArrayEquals(new int[] { 0, 0, 0, 0, 0 }, remainder); + } + } +} diff --git a/common/src/test/org/apache/hadoop/hive/common/type/TestUnsignedInt128.java b/common/src/test/org/apache/hadoop/hive/common/type/TestUnsignedInt128.java new file mode 100644 index 0000000..fbb2aa0 --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestUnsignedInt128.java @@ -0,0 +1,550 @@ +/** + * Copyright (c) Microsoft Corporation + * + * Licensed 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 static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Testcases for {@link UnsignedInt128} + * + * This code was originally written for Microsoft PolyBase. + */ +public class TestUnsignedInt128 { + private UnsignedInt128 zero; + + private UnsignedInt128 one; + + private UnsignedInt128 two; + + @Before + public void setUp() throws Exception { + zero = new UnsignedInt128(0); + one = new UnsignedInt128(1); + two = new UnsignedInt128(2); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testHashCode() { + assertNotEquals(one.hashCode(), two.hashCode()); + assertNotEquals(zero.hashCode(), one.hashCode()); + assertNotEquals(zero.hashCode(), two.hashCode()); + + assertEquals(zero.hashCode(), new UnsignedInt128(0).hashCode()); + assertEquals(one.hashCode(), new UnsignedInt128(1).hashCode()); + assertEquals(two.hashCode(), new UnsignedInt128(2).hashCode()); + } + + private void assertNotEquals(int a, int b) { + assertTrue(a != b); + } + + private void assertNotEquals(long a, long b) { + assertTrue(a != b); + } + + private void assertNotEquals(UnsignedInt128 a, UnsignedInt128 b) { + assertTrue(!a.equals(b)); + } + + @Test + public void testEquals() { + assertNotEquals(one, two); + assertNotEquals(zero, one); + assertNotEquals(zero, two); + + assertEquals(zero, new UnsignedInt128(0)); + assertEquals(one, new UnsignedInt128(1)); + assertEquals(two, new UnsignedInt128(2)); + } + + @Test + public void testCompareTo() { + assertTrue(one.compareTo(two) < 0); + assertTrue(two.compareTo(one) > 0); + assertTrue(one.compareTo(zero) > 0); + assertTrue(zero.compareTo(two) < 0); + } + + @Test + public void testCompareToScaleTen() { + assertTrue(zero.compareToScaleTen(new UnsignedInt128(0), (short) 3) == 0); + assertTrue(zero.compareToScaleTen(new UnsignedInt128(0), (short) -1) == 0); + assertTrue(zero.compareToScaleTen(new UnsignedInt128(0), (short) 12) == 0); + assertTrue(one.compareToScaleTen(zero, (short) 0) > 0); + assertTrue(one.compareToScaleTen(zero, (short) 3) > 0); + assertTrue(one.compareToScaleTen(zero, (short) -3) > 0); + + assertTrue(zero.compareToScaleTen(one, (short) 3) < 0); + assertTrue(zero.compareToScaleTen(one, (short) 0) < 0); + assertTrue(zero.compareToScaleTen(one, (short) -1) == 0); + + assertTrue(new UnsignedInt128(30).compareToScaleTen(new UnsignedInt128(3), + (short) 1) == 0); + assertTrue(new UnsignedInt128(30).compareToScaleTen(new UnsignedInt128(3), + (short) 2) < 0); + assertTrue(new UnsignedInt128(30).compareToScaleTen(new UnsignedInt128(3), + (short) 0) > 0); + + assertTrue(new UnsignedInt128(680000000000L).compareToScaleTen( + new UnsignedInt128(68), (short) 10) == 0); + assertTrue(new UnsignedInt128(68).compareToScaleTen(new UnsignedInt128( + 680000000000L), (short) -10) == 0); + assertTrue(new UnsignedInt128(680000000000L).compareToScaleTen( + new UnsignedInt128(0), (short) 60) > 0); + assertTrue(new UnsignedInt128(680000000000L).compareToScaleTen( + new UnsignedInt128(0), (short) 30) > 0); + assertTrue(new UnsignedInt128(680000000000L).compareToScaleTen( + new UnsignedInt128(0), (short) 10) > 0); + assertTrue(new UnsignedInt128(0).compareToScaleTen(new UnsignedInt128( + 680000000000L), (short) -10) < 0); + assertTrue(new UnsignedInt128(0).compareToScaleTen(new UnsignedInt128( + 680000000000L), (short) -11) < 0); + assertTrue(new UnsignedInt128(0).compareToScaleTen(new UnsignedInt128( + 680000000000L), (short) -12) < 0); + assertTrue(new UnsignedInt128(0).compareToScaleTen(new UnsignedInt128( + 680000000000L), (short) -13) == 0); + assertTrue(new UnsignedInt128(0).compareToScaleTen(new UnsignedInt128( + 680000000000L), (short) -30) == 0); + + assertTrue(new UnsignedInt128(680000000000L).compareToScaleTen( + new UnsignedInt128(680000000001L), (short) 0) < 1); + assertTrue(new UnsignedInt128(68000000000L).compareToScaleTen( + new UnsignedInt128(680000000001L), (short) -1) == 0); + assertTrue(new UnsignedInt128(68000000000L).compareToScaleTen( + new UnsignedInt128(680000000000L), (short) -1) == 0); + assertTrue(new UnsignedInt128(68000000000L).compareToScaleTen( + new UnsignedInt128(679999999999L), (short) -1) == 0); + + assertTrue(new UnsignedInt128(0x10000000000000L).shiftLeftConstructive(32) + .compareToScaleTen( + new UnsignedInt128(0xA0000000000000L).shiftLeftConstructive(32), + (short) -1) == 0); + assertTrue(new UnsignedInt128(0x10000000000000L).shiftLeftConstructive(32) + .compareToScaleTen( + new UnsignedInt128(0xA0000000000000L).shiftLeftConstructive(32), + (short) 1) < 0); + assertTrue(new UnsignedInt128(0x10000000000000L).shiftLeftConstructive(32) + .compareToScaleTen( + new UnsignedInt128(0xA0000000000000L).shiftLeftConstructive(32), + (short) 0) < 0); + assertTrue(new UnsignedInt128(0x10000000000000L).shiftLeftConstructive(32) + .compareToScaleTen( + new UnsignedInt128(0xA0000000000000L).shiftLeftConstructive(32), + (short) -2) > 0); + } + + @Test + public void testToFormalString() { + assertEquals("0", zero.toFormalString()); + assertEquals("1", one.toFormalString()); + + assertEquals("30", new UnsignedInt128(30).toFormalString()); + assertEquals("680000000000", + new UnsignedInt128(680000000000L).toFormalString()); + assertEquals("6800000000000", + new UnsignedInt128(6800000000000L).toFormalString()); + assertEquals("68", new UnsignedInt128(68).toFormalString()); + + assertEquals(zero, new UnsignedInt128("0")); + assertEquals(one, new UnsignedInt128("1")); + + assertEquals(new UnsignedInt128(30), new UnsignedInt128("30")); + assertEquals(new UnsignedInt128(680000000000L), new UnsignedInt128( + "680000000000")); + assertEquals(new UnsignedInt128(6800000000000L), new UnsignedInt128( + "6800000000000")); + assertEquals(new UnsignedInt128(68), new UnsignedInt128("68")); + } + + @Test + public void testUnsignedInt128() { + assertEquals(0L, new UnsignedInt128().asLong()); + } + + @Test + public void testUnsignedInt128UnsignedInt128() { + assertEquals(1L, new UnsignedInt128(one).asLong()); + assertEquals(2L, new UnsignedInt128(two).asLong()); + } + + @Test + public void testUnsignedInt128IntIntIntInt() { + assertEquals(((long) 11) << 32L | 23L, + new UnsignedInt128(23, 11, 0, 0).asLong()); + } + + @Test + public void testZeroClear() { + assertFalse(one.isZero()); + assertFalse(two.isZero()); + assertNotEquals(0L, one.asLong()); + assertNotEquals(0L, two.asLong()); + + two.zeroClear(); + + assertNotEquals(0L, one.asLong()); + assertEquals(0L, two.asLong()); + assertFalse(one.isZero()); + assertTrue(two.isZero()); + + one.zeroClear(); + + assertEquals(0L, one.asLong()); + assertEquals(0L, two.asLong()); + assertTrue(one.isZero()); + assertTrue(two.isZero()); + } + + @Test + public void testAddDestructive() { + one.addDestructive(two); + assertEquals(3L, one.asLong()); + assertEquals(2L, two.asLong()); + + UnsignedInt128 big = new UnsignedInt128((1L << 62) + 3L); + UnsignedInt128 tmp = new UnsignedInt128(0L); + for (int i = 0; i < 54; ++i) { + tmp.addDestructive(big); + } + + assertEquals(3 * 54, tmp.getV0()); + assertEquals(0x80000000, tmp.getV1()); // (54 % 4) << 62 + assertEquals(13, tmp.getV2()); // 54/4 + assertEquals(0, tmp.getV3()); + + assertEquals((1L << 62) + 3L, big.asLong()); + + UnsignedInt128 huge = one.shiftLeftConstructive(127); + UnsignedInt128 huge2 = one.shiftLeftConstructive(127); + try { + huge2.addDestructive(huge); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testSubtractDestructive() { + two.subtractDestructive(one); + assertEquals(1L, one.asLong()); + assertEquals(1L, one.asLong()); + + try { + one.subtractDestructive(new UnsignedInt128(10L)); + fail(); + } catch (ArithmeticException ex) { + // ok + } + + UnsignedInt128 big = new UnsignedInt128((1L << 62) + (3L << 34) + 3L); + big.shiftLeftDestructive(6); + UnsignedInt128 tmp = new UnsignedInt128((1L << 61) + 5L); + tmp.shiftLeftDestructive(6); + + big.subtractDestructive(tmp); + big.subtractDestructive(tmp); + + assertEquals((3 << 6) - 2 * (5 << 6), big.getV0()); + assertEquals((3 << 8) - 1, big.getV1()); + assertEquals(0, big.getV2()); + assertEquals(0, big.getV3()); + } + + @Test + public void testMultiplyDestructiveInt() { + two.multiplyDestructive(1); + assertEquals(2L, two.asLong()); + assertEquals(1L, one.asLong()); + two.multiplyDestructive(2); + assertEquals(4L, two.asLong()); + + UnsignedInt128 five = new UnsignedInt128(5); + five.multiplyDestructive(6432346); + assertEquals(6432346 * 5, five.getV0()); + assertEquals(0, five.getV1()); + assertEquals(0, five.getV2()); + assertEquals(0, five.getV3()); + + UnsignedInt128 big = new UnsignedInt128((1L << 62) + (3L << 34) + 3L); + big.multiplyDestructive(96); + + assertEquals(3 * 96, big.getV0()); + assertEquals(96 * (3 << 2), big.getV1()); + assertEquals(96 / 4, big.getV2()); + assertEquals(0, big.getV3()); + + UnsignedInt128 tmp = new UnsignedInt128(1); + tmp.shiftLeftDestructive(126); + tmp.multiplyDestructive(2); + try { + tmp.multiplyDestructive(2); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testShiftDestructive() { + UnsignedInt128 big = new UnsignedInt128((1L << 62) + (23L << 32) + 89L); + big.shiftLeftDestructive(2); + + assertEquals(89 * 4, big.getV0()); + assertEquals(23 * 4, big.getV1()); + assertEquals(1, big.getV2()); + assertEquals(0, big.getV3()); + + big.shiftLeftDestructive(32); + + assertEquals(0, big.getV0()); + assertEquals(89 * 4, big.getV1()); + assertEquals(23 * 4, big.getV2()); + assertEquals(1, big.getV3()); + + big.shiftRightDestructive(2, true); + + assertEquals(0, big.getV0()); + assertEquals(89, big.getV1()); + assertEquals(23 + (1 << 30), big.getV2()); + assertEquals(0, big.getV3()); + + big.shiftRightDestructive(32, true); + + assertEquals(89, big.getV0()); + assertEquals(23 + (1 << 30), big.getV1()); + assertEquals(0, big.getV2()); + assertEquals(0, big.getV3()); + + // test rounding + UnsignedInt128 tmp = new UnsignedInt128(17); + assertEquals(17, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(9, tmp.getV0()); + tmp.shiftRightDestructive(1, false); + assertEquals(4, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(2, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(1, tmp.getV0()); + tmp.shiftRightDestructive(1, true); + assertEquals(1, tmp.getV0()); + tmp.shiftRightDestructive(1, false); + assertEquals(0, tmp.getV0()); + } + + @Test + public void testMultiplyDestructiveUnsignedInt128() { + two.multiplyDestructive(one); + assertEquals(2L, two.asLong()); + assertEquals(1L, one.asLong()); + two.multiplyDestructive(two); + assertEquals(4L, two.asLong()); + + UnsignedInt128 five = new UnsignedInt128(5); + five.multiplyDestructive(new UnsignedInt128(6432346)); + assertEquals(6432346 * 5, five.getV0()); + assertEquals(0, five.getV1()); + assertEquals(0, five.getV2()); + assertEquals(0, five.getV3()); + + UnsignedInt128 big = new UnsignedInt128((1L << 62) + (3L << 34) + 3L); + big.multiplyDestructive(new UnsignedInt128(96)); + + assertEquals(3 * 96, big.getV0()); + assertEquals(96 * (3 << 2), big.getV1()); + assertEquals(96 / 4, big.getV2()); + assertEquals(0, big.getV3()); + + UnsignedInt128 tmp = new UnsignedInt128(1); + tmp.shiftLeftDestructive(126); + tmp.multiplyDestructive(new UnsignedInt128(2)); + try { + tmp.multiplyDestructive(new UnsignedInt128(2)); + fail(); + } catch (ArithmeticException ex) { + // ok + } + + UnsignedInt128 complicated1 = new UnsignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + UnsignedInt128 complicated2 = new UnsignedInt128(54234234, 9, 0, 0); + BigInteger bigInteger2 = complicated2.toBigIntegerSlow(); + complicated1.multiplyDestructive(complicated2); + BigInteger ans = bigInteger1.multiply(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + + try { + UnsignedInt128 complicated3 = new UnsignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0); + complicated3 + .multiplyDestructive(new UnsignedInt128(54234234, 9845, 0, 0)); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testMultiplyScaleDownTenDestructiveScaleTen() { + for (int scale = 0; scale < 38; ++scale) { + UnsignedInt128 right = new UnsignedInt128(1); + right.scaleUpTenDestructive((short) scale); + { + // 10000000....000 + UnsignedInt128 leftJust = new UnsignedInt128(1); + leftJust.scaleUpTenDestructive((short) 15); + UnsignedInt128 leftInc = leftJust.incrementConstructive(); + UnsignedInt128 leftDec = leftJust.decrementConstructive(); + + if (scale + 10 <= 38) { + leftJust.multiplyScaleDownTenDestructive(right, (short) (scale + 10)); + assertEquals("scale=" + scale, 100000L, leftJust.asLong()); + leftInc.multiplyScaleDownTenDestructive(right, (short) (scale + 10)); + assertEquals("scale=" + scale, 100000L, leftInc.asLong()); + leftDec.multiplyScaleDownTenDestructive(right, (short) (scale + 10)); + assertEquals("scale=" + scale, 100000L, leftDec.asLong()); + } else { + leftJust.multiplyScaleDownTenDestructive(right, (short) (scale + 10)); + assertEquals("scale=" + scale, 0L, leftJust.asLong()); + leftInc.multiplyScaleDownTenDestructive(right, (short) (scale + 10)); + assertEquals("scale=" + scale, 0L, leftInc.asLong()); + leftDec.multiplyScaleDownTenDestructive(right, (short) (scale + 10)); + assertEquals("scale=" + scale, 0L, leftDec.asLong()); + } + } + + { + // 10000500....00 + UnsignedInt128 leftHalfJust = new UnsignedInt128(1); + leftHalfJust.scaleUpTenDestructive((short) 6); + leftHalfJust.addDestructive(new UnsignedInt128(5)); + leftHalfJust.scaleUpTenDestructive((short) 9); + UnsignedInt128 leftHalfInc = leftHalfJust.incrementConstructive(); + UnsignedInt128 leftHalfDec = leftHalfJust.decrementConstructive(); + + if (scale + 10 <= 38) { + leftHalfJust.multiplyScaleDownTenDestructive(right, + (short) (scale + 10)); + assertEquals("scale=" + scale, 100001L, leftHalfJust.asLong()); + leftHalfInc.multiplyScaleDownTenDestructive(right, + (short) (scale + 10)); + assertEquals("scale=" + scale, 100001L, leftHalfInc.asLong()); + leftHalfDec.multiplyScaleDownTenDestructive(right, + (short) (scale + 10)); + assertEquals("scale=" + scale, 100000L, leftHalfDec.asLong()); + } else { + leftHalfJust.multiplyScaleDownTenDestructive(right, + (short) (scale + 10)); + assertEquals("scale=" + scale, 0L, leftHalfJust.asLong()); + leftHalfInc.multiplyScaleDownTenDestructive(right, + (short) (scale + 10)); + assertEquals("scale=" + scale, 0L, leftHalfInc.asLong()); + leftHalfDec.multiplyScaleDownTenDestructive(right, + (short) (scale + 10)); + assertEquals("scale=" + scale, 0L, leftHalfDec.asLong()); + } + } + } + } + + @Test + public void testDivideDestructiveInt() { + two.divideDestructive(1); + assertEquals(1L, one.asLong()); + assertEquals(2L, two.asLong()); + one.divideDestructive(2); + assertEquals(0L, one.asLong()); + assertEquals(2L, two.asLong()); + + UnsignedInt128 var1 = new UnsignedInt128(1234234662345L); + var1.divideDestructive(642337); + assertEquals(1234234662345L / 642337L, var1.asLong()); + + UnsignedInt128 complicated1 = new UnsignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + complicated1.divideDestructive(1534223465); + BigInteger bigInteger2 = BigInteger.valueOf(1534223465); + BigInteger ans = bigInteger1.divide(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + + try { + complicated1.divideDestructive(0); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testDivideDestructiveUnsignedInt128() { + UnsignedInt128 remainder = new UnsignedInt128(); + two.divideDestructive(one, remainder); + assertEquals(1L, one.asLong()); + assertEquals(2L, two.asLong()); + assertEquals(zero, remainder); + one.divideDestructive(two, remainder); + assertEquals(0L, one.asLong()); + assertEquals(2L, two.asLong()); + assertEquals(new UnsignedInt128(1), remainder); + + UnsignedInt128 var1 = new UnsignedInt128(1234234662345L); + var1.divideDestructive(new UnsignedInt128(642337), remainder); + assertEquals(1234234662345L / 642337L, var1.asLong()); + assertEquals(1234234662345L % 642337L, remainder.asLong()); + + UnsignedInt128 complicated1 = new UnsignedInt128(0xF9892FCA, 0x59D109AD, + 0x0534AB4C, 0x42395ADC); + UnsignedInt128 complicated2 = new UnsignedInt128(0xF09DC19A, 0x00001234, 0, + 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + BigInteger bigInteger2 = complicated2.toBigIntegerSlow(); + complicated1.divideDestructive(complicated2, remainder); + BigInteger ans = bigInteger1.divide(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + + try { + complicated1.divideDestructive(zero, remainder); + fail(); + } catch (ArithmeticException ex) { + // ok + } + } + + @Test + public void testDivideDestructiveUnsignedInt128Again() { + UnsignedInt128 complicated1 = new UnsignedInt128(0xF9892FCA, 0x59D109AD, 0, + 0); + UnsignedInt128 complicated2 = new UnsignedInt128(0xF09DC19A, 3, 0, 0); + BigInteger bigInteger1 = complicated1.toBigIntegerSlow(); + BigInteger bigInteger2 = complicated2.toBigIntegerSlow(); + complicated1.divideDestructive(complicated2, new UnsignedInt128()); + BigInteger ans = bigInteger1.divide(bigInteger2); + assertEquals(ans, complicated1.toBigIntegerSlow()); + } +}