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

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

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

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

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

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

+ *

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

+ *

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

+ *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ *

This method does not modify this object.

+ *

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

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

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

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

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

+ *

This method does not modify this object.

+ *

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

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

Summary of this method + *

+ *

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

+ *

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

+ * @param array array to scale down. in AND out. length must be 8. + * @param tenScale distance to scale down + */ + private static void scaleDownTenArray8RoundUp(int[] array, short tenScale) { + assert (array.length == 8); + if (tenScale > MAX_DIGITS) { + // then definitely this will end up zero. + Arrays.fill(array, 0); + return; + } + if (tenScale <= SqlMathUtil.MAX_POWER_TEN_INT31) { + int divisor = SqlMathUtil.POWER_TENS_INT31[tenScale]; + assert (divisor > 0); + boolean round = divideCheckRound(array, divisor); + if (round) { + incrementArray(array); + } + return; + } + + // division by inverse multiplication. + final int[] inverse = SqlMathUtil.INVERSE_POWER_TENS_INT128[tenScale].v; + final int inverseWordShift = SqlMathUtil.INVERSE_POWER_TENS_INT128_WORD_SHIFTS[tenScale]; + assert (inverseWordShift <= 3); + assert (inverse[3] != 0); + for (int i = 5 + inverseWordShift; i < 8; ++i) { + if (array[i] != 0) { + SqlMathUtil.throwOverflowException(); // because inverse[3] is + // not zero + } + } + + int z4 = 0, z5 = 0, z6 = 0, z7 = 0; // because inverse is scaled 2^128, + // these will become v0-v3 + int z8 = 0, z9 = 0, z10 = 0; // for wordshift + long product = 0L; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[3] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[2] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[1] & SqlMathUtil.LONG_MASK); + z4 = (int) product; + product >>>= 32; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[3] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[2] & SqlMathUtil.LONG_MASK); + z5 = (int) product; + product >>>= 32; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[3] & SqlMathUtil.LONG_MASK); + z6 = (int) product; + product >>>= 32; + + product += (inverse[0] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK) + + (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[4] & SqlMathUtil.LONG_MASK); + z7 = (int) product; + product >>>= 32; + + if (inverseWordShift >= 1) { + product += (inverse[1] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK) + + (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[5] & SqlMathUtil.LONG_MASK); + z8 = (int) product; + product >>>= 32; + + if (inverseWordShift >= 2) { + product += (inverse[2] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK) + + (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[6] & SqlMathUtil.LONG_MASK); + z9 = (int) product; + product >>>= 32; + + if (inverseWordShift >= 3) { + product += (inverse[3] & SqlMathUtil.LONG_MASK) + * (array[7] & SqlMathUtil.LONG_MASK); + z10 = (int) product; + product >>>= 32; + } + } + } + + if (product != 0) { + SqlMathUtil.throwOverflowException(); + } + + // if inverse is word-shifted for accuracy, shift it back here. + switch (inverseWordShift) { + case 1: + z4 = z5; + z5 = z6; + z6 = z7; + z7 = z8; + break; + case 2: + z4 = z6; + z5 = z7; + z6 = z8; + z7 = z9; + break; + case 3: + z4 = z7; + z5 = z8; + z6 = z9; + z7 = z10; + break; + default: + break; + } + + // now, correct +1 error and rounding up. + final int[] power = SqlMathUtil.POWER_TENS_INT128[tenScale].v; + final int[] half = SqlMathUtil.ROUND_POWER_TENS_INT128[tenScale].v; + + int d0, d1, d2, d3, d4; + product = (array[0] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) + * (z4 & SqlMathUtil.LONG_MASK); + d0 = (int) product; + + product = (array[1] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) + * (z5 & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) + * (z4 & SqlMathUtil.LONG_MASK) - ((int) -(product >> 32)); + d1 = (int) product; + + product = (array[2] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) + * (z6 & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) + * (z5 & SqlMathUtil.LONG_MASK) + - (power[2] & SqlMathUtil.LONG_MASK) + * (z4 & SqlMathUtil.LONG_MASK) - ((int) -(product >> 32)); + d2 = (int) product; + + product = (array[3] & SqlMathUtil.LONG_MASK) + - (power[0] & SqlMathUtil.LONG_MASK) + * (z7 & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) + * (z6 & SqlMathUtil.LONG_MASK) + - (power[2] & SqlMathUtil.LONG_MASK) + * (z5 & SqlMathUtil.LONG_MASK) + - (power[3] & SqlMathUtil.LONG_MASK) + * (z4 & SqlMathUtil.LONG_MASK) - ((int) -(product >> 32)); + d3 = (int) product; + + product = (array[4] & SqlMathUtil.LONG_MASK) + - (power[1] & SqlMathUtil.LONG_MASK) + * (z7 & SqlMathUtil.LONG_MASK) + - (power[2] & SqlMathUtil.LONG_MASK) + * (z6 & SqlMathUtil.LONG_MASK) + - (power[3] & SqlMathUtil.LONG_MASK) + * (z5 & SqlMathUtil.LONG_MASK) - ((int) -(product >> 32)); + d4 = (int) product; + + // If the difference is larger than 2^128 (d4 != 0), then D is + // definitely larger than power, so increment. + // otherwise, compare it with power and half. + boolean increment = (d4 != 0) + || (compareTo(d0, d1, d2, d3, half[0], half[1], half[2], + half[3]) >= 0); + + array[0] = z4; + array[1] = z5; + array[2] = z6; + array[3] = z7; + if (increment) { + incrementArray(array); + } + } + + /** + * Scales down the given array for 5**fiveScale. + * @param array array to scale down + * @param fiveScale distance to scale down + * @return Whether it requires incrementing if rounding + */ + private static boolean scaleDownFiveArray(int[] array, short fiveScale) { + while (true) { + int powerFive = Math.min(fiveScale, + SqlMathUtil.MAX_POWER_FIVE_INT31); + fiveScale -= powerFive; + + int divisor = SqlMathUtil.POWER_FIVES_INT31[powerFive]; + assert (divisor > 0); + if (fiveScale == 0) { + return divideCheckRound(array, divisor); + } else { + divideCheckRound(array, divisor); + } + } + } + + private static boolean divideCheckRound(int[] array, int divisor) { + long remainder = 0; + for (int i = array.length - 1; i >= 0; --i) { + remainder = ((array[i] & SqlMathUtil.LONG_MASK) + (remainder << 32)); + array[i] = (int) (remainder / divisor); + remainder %= divisor; + } + + return (remainder >= (divisor >> 1)); + } + + private static void scaleDownFiveArrayRoundUp(int[] array, short tenScale) { + boolean rounding = scaleDownFiveArray(array, tenScale); + if (rounding) { + incrementArray(array); + } + } + + /** + * Internal method to apply the result of multiplication with right-shifting. This method does round the value while + * right-shifting (SQL Numeric semantics). + * @param rightShifts distance of right-shifts + */ + private static void shiftRightArray(int rightShifts, int[] z, int[] result, + boolean round) { + assert (rightShifts >= 0); + if (rightShifts == 0) { + for (int i = 0; i < INT_COUNT; ++i) { + if (z[i + INT_COUNT] != 0) { + SqlMathUtil.throwOverflowException(); + } + } + result[0] = z[0]; + result[1] = z[1]; + result[2] = z[2]; + result[3] = z[3]; + } else { + final int wordShifts = rightShifts / 32; + final int bitShiftsInWord = rightShifts % 32; + final int shiftRestore = 32 - bitShiftsInWord; + // check this because "123 << 32" will be 123. + final boolean noRestore = bitShiftsInWord == 0; + + // overflow checks + if (z.length > INT_COUNT) { + if (wordShifts + INT_COUNT < z.length + && (z[wordShifts + INT_COUNT] >>> bitShiftsInWord) != 0) { + SqlMathUtil.throwOverflowException(); + } + + for (int i = 1; i < INT_COUNT; ++i) { + if (i + wordShifts < z.length - INT_COUNT + && z[i + wordShifts + INT_COUNT] != 0) { + SqlMathUtil.throwOverflowException(); + } + } + } + + // check round-ups before settings values to result. + // be aware that result could be the same object as z. + boolean roundCarry = false; + if (round) { + if (bitShiftsInWord == 0) { + assert (wordShifts > 0); + roundCarry = z[wordShifts - 1] < 0; + } else { + roundCarry = (z[wordShifts] & (1 << (bitShiftsInWord - 1))) != 0; + } + } + + // extract the values. + for (int i = 0; i < INT_COUNT; ++i) { + int val = 0; + if (!noRestore && i + wordShifts + 1 < z.length) { + val = z[i + wordShifts + 1] << shiftRestore; + } + if (i + wordShifts < z.length) { + val |= (z[i + wordShifts] >>> bitShiftsInWord); + } + result[i] = val; + } + + if (roundCarry) { + incrementArray(result); + } + } + } + + /** + * helper method for multiplication. used when either left/right fits int32. + */ + private void multiplyDestructiveFitsInt32(UnsignedInt128 right, + short rightShifts, short tenScaleDown) { + assert (this.fitsInt32() && right.fitsInt32()); + assert (rightShifts == 0 || tenScaleDown == 0); // only one of them + if (this.isZero()) { + return; // zero. no need to shift/scale + } else if (right.isZero()) { + zeroClear(); + return; // zero. no need to shift/scale + } else if (this.isOne()) { + this.update(right); + } else { + this.multiplyDestructive(right.v[0]); + } + + if (rightShifts > 0) { + this.shiftRightDestructive(rightShifts, true); + } else if (tenScaleDown > 0) { + this.scaleDownTenDestructive(tenScaleDown); + } + } + + /** Updates the value of {@link #cnt} by checking {@link #v}. */ + private void updateCount() { + if (v[3] != 0) { + this.count = (byte) 4; + } else if (v[2] != 0) { + this.count = (byte) 3; + } else if (v[1] != 0) { + this.count = (byte) 2; + } else if (v[0] != 0) { + this.count = (byte) 1; + } else { + this.count = (byte) 0; + } + } +} diff --git a/common/src/test/org/apache/hadoop/hive/common/type/Decimal128Test.java b/common/src/test/org/apache/hadoop/hive/common/type/Decimal128Test.java new file mode 100644 index 0000000..a0abdd0 --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/Decimal128Test.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 Decimal128Test { + 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/SignedInt128Test.java b/common/src/test/org/apache/hadoop/hive/common/type/SignedInt128Test.java new file mode 100644 index 0000000..a060c97 --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/SignedInt128Test.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 SignedInt128Test { + 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/SqlMathUtilTest.java b/common/src/test/org/apache/hadoop/hive/common/type/SqlMathUtilTest.java new file mode 100644 index 0000000..bc20a3e --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/SqlMathUtilTest.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 SqlMathUtilTest { + @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/UnsignedInt128Test.java b/common/src/test/org/apache/hadoop/hive/common/type/UnsignedInt128Test.java new file mode 100644 index 0000000..8047d39 --- /dev/null +++ b/common/src/test/org/apache/hadoop/hive/common/type/UnsignedInt128Test.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 UnsignedInt128Test { + 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()); + } +}