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..4f886f0 --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/type/Decimal128.java @@ -0,0 +1,1331 @@ +/** +* Copyright 2013 Microsoft +* +* 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+ * 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..97582b5 --- /dev/null +++ b/common/src/java/org/apache/hadoop/hive/common/type/SignedInt128.java @@ -0,0 +1,856 @@ +/** +* Copyright 2013 Microsoft +* +* 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 + Comparableresult := 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. + * 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..dfaf1cc --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestDecimal128.java @@ -0,0 +1,413 @@ +/** +* Copyright 2013 Microsoft +* +* 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); + } + // System.out.println(val); + } + + @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..d8ac65e --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestSignedInt128.java @@ -0,0 +1,450 @@ +/** +* Copyright 2013 Microsoft +* +* 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..7de76a1 --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestSqlMathUtil.java @@ -0,0 +1,51 @@ +/** +* Copyright 2013 Microsoft +* +* 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); + // System.out.println(Arrays.toString(quotient)); + // System.out.println(Arrays.toString(remainder)); + 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..30aff41 --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/TestUnsignedInt128.java @@ -0,0 +1,569 @@ +/** +* Copyright 2013 Microsoft +* +* 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()); + } +}