org.apache.hudi.org.apache.hadoop_hive.common.type.Decimal128 Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hive.common.type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.apache.hive.common.util.Decimal128FastBuffer;
/**
* This code was based on code from Microsoft's 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;
public static final Decimal128 ONE = new Decimal128().update(1);
/** Maximum value that can be represented in this class. */
public static final Decimal128 MAX_VALUE = new Decimal128(
UnsignedInt128.TEN_TO_THIRTYEIGHT, (short) 0, false).subtractDestructive(
Decimal128.ONE, (short) 0);
/** Minimum value that can be represented in this class. */
public static final Decimal128 MIN_VALUE = new Decimal128(
UnsignedInt128.TEN_TO_THIRTYEIGHT, (short) 0, true).addDestructive(
Decimal128.ONE, (short) 0);
/** For Serializable. */
private static final long serialVersionUID = 1L;
/**
* The unscaled value of this Decimal128, as returned by
* {@link #getUnscaledValue()}.
*
* @serial
* @see #getUnscaledValue()
*/
private 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 Decimal128 zeroClear() {
this.unscaledValue.zeroClear();
this.signum = 0;
return this;
}
/** @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 Decimal128 update(Decimal128 o) {
this.unscaledValue.update(o.unscaledValue);
this.scale = o.scale;
this.signum = o.signum;
return this;
}
/**
* Copy the value of given object and assigns a custom scale.
*
* @param o
* object to copy from
*/
public Decimal128 update(Decimal128 o, short scale) {
update(o);
this.changeScaleDestructive(scale);
return this;
}
/**
* 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 Decimal128 update(long val) {
return 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 Decimal128 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);
}
return this;
}
/**
* 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 Decimal128 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;
}
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));
}
}
return this;
}
/**
* 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 Decimal128 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());
return this;
}
/**
* 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 Decimal128 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());
return this;
}
/**
* Updates the value of this object by reading from ByteBuffer, receiving only
* 96+32 bits data.
*
* @param buf
* ByteBuffer to read values from
*/
public Decimal128 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());
return this;
}
/**
* Updates the value of this object by reading from ByteBuffer, receiving only
* 64+32 bits data.
*
* @param buf
* ByteBuffer to read values from
*/
public Decimal128 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());
return this;
}
/**
* Updates the value of this object by reading from ByteBuffer, receiving only
* 32+32 bits data.
*
* @param buf
* ByteBuffer to read values from
*/
public Decimal128 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());
return this;
}
/**
* 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 Decimal128 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);
return this;
}
/**
* 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 Decimal128 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);
return this;
}
/**
* 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 Decimal128 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);
return this;
}
/**
* 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 Decimal128 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);
return this;
}
/**
* 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 Decimal128 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);
return this;
}
/**
* Updates the value of this object with the given {@link BigDecimal}.
* @param bigDecimal
* {@link java.math.BigDecimal}
*/
public Decimal128 update(BigDecimal bigDecimal) {
return update(bigDecimal.unscaledValue(), (short) bigDecimal.scale());
}
/**
* Updates the value of this object with the given {@link BigInteger} and scale.
*
* @param bigInt
* {@link java.math.BigInteger}
* @param scale
*/
public Decimal128 update(BigInteger bigInt, short scale) {
this.scale = scale;
this.signum = (byte) bigInt.compareTo(BigInteger.ZERO);
if (signum == 0) {
update(0);
} else if (signum < 0) {
unscaledValue.update(bigInt.negate());
} else {
unscaledValue.update(bigInt);
}
return this;
}
/**
* Updates the value of this object with the given string.
*
* @param str
* string
* @param scale
* scale of the {@code Decimal128}.
*/
public Decimal128 update(String str, short scale) {
return 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 Decimal128 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 this;
}
// "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));
return this;
}
/**
* Serializes the value in a format compatible with the BigDecimal's own representation
* @param scratch
*/
public int fastSerializeForHiveDecimal( Decimal128FastBuffer scratch) {
return this.unscaledValue.fastSerializeForHiveDecimal(scratch, this.signum);
}
/**
* 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 Decimal128 addDestructive(Decimal128 right, short scale) {
this.changeScaleDestructive(scale);
if (right.signum == 0) {
return this;
}
if (this.signum == 0) {
this.update(right);
this.changeScaleDestructive(scale);
return this;
}
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();
return this;
}
/**
* 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 Decimal128 subtractDestructive(Decimal128 right, short scale) {
this.changeScaleDestructive(scale);
if (right.signum == 0) {
return this;
}
if (this.signum == 0) {
this.update(right);
this.changeScaleDestructive(scale);
this.negateDestructive();
return this;
}
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();
return this;
}
/**
* 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);
}
/**
* As of 2/11/2014 this has a known bug in multiplication. See
* TestDecimal128.testKnownPriorErrors(). Keeping this source
* code available since eventually it is better to fix this.
* The fix employed now is to replace this code with code that
* uses Java BigDecimal multiply.
*
* 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 multiplyDestructiveNativeDecimal128(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 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) {
HiveDecimal rightHD = HiveDecimal.create(right.toBigDecimal());
HiveDecimal thisHD = HiveDecimal.create(this.toBigDecimal());
HiveDecimal result = thisHD.multiply(rightHD);
/* If the result is null, throw an exception. This can be caught
* by calling code in the vectorized code path and made to yield
* a SQL NULL value.
*/
if (result == null) {
throw new ArithmeticException("null multiply result");
}
this.update(result.bigDecimalValue().toPlainString(), newScale);
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 scale
* scale of the result. must be 0 or positive.
*/
public static void divide(Decimal128 left, Decimal128 right,
Decimal128 quotient, 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);
}
/**
* As of 1/20/2014 this has a known bug in division. See
* TestDecimal128.testKnownPriorErrors(). Keeping this source
* code available since eventually it is better to fix this.
* The fix employed now is to replace this code with code that
* uses Java BigDecimal divide.
*
* 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 divideDestructiveNativeDecimal128(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();
}
/**
* Divide the target object by right, and scale the result to newScale.
*
* This uses HiveDecimal to get a correct answer with the same rounding
* behavior as HiveDecimal, but it is expensive.
*
* In the future, a native implementation could be faster.
*/
public void divideDestructive(Decimal128 right, short newScale) {
HiveDecimal rightHD = HiveDecimal.create(right.toBigDecimal());
HiveDecimal thisHD = HiveDecimal.create(this.toBigDecimal());
HiveDecimal result = thisHD.divide(rightHD);
/* If the result is null, throw an exception. This can be caught
* by calling code in the vectorized code path and made to yield
* a SQL NULL value.
*/
if (result == null) {
throw new ArithmeticException("null divide result");
}
this.update(result.bigDecimalValue().toPlainString(), newScale);
this.unscaledValue.throwIfExceedsTenToThirtyEight();
}
/**
* Performs decimal modulo
*
* The definition of modulo (x % p) is:
* x - IntegerPart(x / p, resultScale) * p
*
*
* @left
* is x
* @right
* is p
* @result
* receives the result
* @scratch
* scratch space to avoid need to create a new object
* @scale
* scale of result
*/
public static void modulo(Decimal128 left, Decimal128 right, Decimal128 result,
short scale) {
// set result to x / p (the quotient)
Decimal128.divide(left, right, result, scale);
// take integer part of it
result.zeroFractionPart();
// multiply by p
result.multiplyDestructive(right, scale);
// negate it
result.negateDestructive();
// add x to it
result.addDestructive(left, scale);
}
/**
* 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(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() {
// Avoid allocating temporary variables for special cases: signum or scale is zero
if (signum == 0) {
return 0L;
}
if (scale == 0) {
long ret;
ret = this.unscaledValue.getV1();
ret <<= 32L;
ret |= SqlMathUtil.LONG_MASK & this.unscaledValue.getV0();
if (signum >= 0) {
return ret;
} else {
return -ret;
}
} else {
HiveDecimal hd = HiveDecimal.create(this.toBigDecimal());
return hd.longValue();
}
}
/**
* 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");
}
}
/**
* Temporary array used in {@link #getHiveDecimalString}
*/
private int [] tmpArray = new int[2];
/**
* Returns the string representation of this value. It discards the trailing zeros
* in the fractional part to match the HiveDecimal's string representation. However,
* don't use this string representation for the reconstruction of the object.
*
* @return string representation of this value
*/
public String getHiveDecimalString() {
if (this.signum == 0) {
return "0";
}
StringBuilder buf = new StringBuilder(50);
if (this.signum < 0) {
buf.append('-');
}
char [] unscaled = this.unscaledValue.getDigitsArray(tmpArray);
int unscaledLength = tmpArray[0];
int trailingZeros = tmpArray[1];
int numIntegerDigits = unscaledLength - this.scale;
if (numIntegerDigits > 0) {
// write out integer part first
// then write out fractional part
for (int i=0; i < numIntegerDigits; i++) {
buf.append(unscaled[i]);
}
if (this.scale > trailingZeros) {
buf.append('.');
for (int i = numIntegerDigits; i < (unscaledLength - trailingZeros); i++) {
buf.append(unscaled[i]);
}
}
} else {
// no integer part
buf.append('0');
if (this.scale > trailingZeros) {
// fractional part has, starting with zeros
buf.append('.');
for (int i = unscaledLength; i < this.scale; ++i) {
buf.append('0');
}
for (int i = 0; i < (unscaledLength - trailingZeros); i++) {
buf.append(unscaled[i]);
}
}
}
return new String(buf);
}
/**
* 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() + "])";
}
/**
* Vectorized execution uses the smallest possible positive non-zero
* value to prevent possible later zero-divide exceptions. Set the field
* to this value (1 in the internal unsigned 128 bit int).
*/
public void setNullDataValue() {
unscaledValue.update(1, 0, 0, 0);
}
/**
* Update the value to a decimal value with the decimal point equal to
* val but with the decimal point inserted scale
* digits from the right. Behavior is undefined if scale is > 38 or < 0.
*
* For example, updateFixedPoint(123456789L, (short) 3) changes the target
* to the value 123456.789 with scale 3.
*/
public void updateFixedPoint(long val, short scale) {
this.scale = scale;
if (val < 0L) {
this.unscaledValue.update(-val);
this.signum = -1;
} else if (val == 0L) {
zeroClear();
} else {
this.unscaledValue.update(val);
this.signum = 1;
}
}
/**
* Zero the fractional part of value.
*
* Argument scratch is needed to hold unused remainder output, to avoid need to
* create a new object.
*/
public void zeroFractionPart(UnsignedInt128 scratch) {
short placesToRemove = this.getScale();
// If there's no fraction part, return immediately to avoid the cost of a divide.
if (placesToRemove == 0) {
return;
}
/* Divide by a power of 10 equal to 10**scale to logically shift the digits
* places right by "scale" positions to eliminate them.
*/
UnsignedInt128 powerTenDivisor = SqlMathUtil.POWER_TENS_INT128[placesToRemove];
this.getUnscaledValue().divideDestructive(powerTenDivisor, scratch);
/* Multiply by the same power of ten to shift the decimal point back to
* the original place. Places to the right of the decimal will be zero.
*/
this.getUnscaledValue().scaleUpTenDestructive(placesToRemove);
if (this.unscaledValue.isZero()) {
this.signum = 0;
}
}
public void zeroFractionPart() {
/* A scratch variable is created here. This could be optimized in the future
* by perhaps using thread-local storage to allocate this scratch field.
*/
UnsignedInt128 scratch = new UnsignedInt128();
zeroFractionPart(scratch);
}
/**
* Multiplies this with this, updating this
*
* @return self
*/
public Decimal128 squareDestructive() {
this.multiplyDestructive(this, this.getScale());
return this;
}
/**
* For UDAF variance we use the algorithm described by Chan, Golub, and LeVeque in
* "Algorithms for computing the sample variance: analysis and recommendations"
* The American Statistician, 37 (1983) pp. 242--247.
*
* variance = variance1 + variance2 + n/(m*(m+n)) * pow(((m/n)*t1 - t2),2)
*
* where: - variance is sum[x-avg^2] (this is actually n times the variance)
* and is updated at every step. - n is the count of elements in chunk1 - m is
* the count of elements in chunk2 - t1 = sum of elements in chunk1, t2 =
* sum of elements in chunk2.
*
* This is a helper function doing the intermediate computation:
* t = myagg.count*value - myagg.sum;
* myagg.variance += (t*t) / ((double)myagg.count*(myagg.count-1));
*
* @return self
*/
public Decimal128 updateVarianceDestructive(
Decimal128 scratch, Decimal128 value, Decimal128 sum, long count) {
scratch.update(count);
scratch.multiplyDestructive(value, value.getScale());
scratch.subtractDestructive(sum, sum.getScale());
scratch.squareDestructive();
scratch.unscaledValue.divideDestructive(count * (count-1));
this.addDestructive(scratch, getScale());
return this;
}
/**
* Fats update from BigInteger two's complement representation
* @param internalStorage BigInteger two's complement representation of the unscaled value
* @param scale
*/
public Decimal128 fastUpdateFromInternalStorage(byte[] internalStorage, short scale) {
this.scale = scale;
this.signum = this.unscaledValue.fastUpdateFromInternalStorage(internalStorage);
return this;
}
/**
* This setter is only for de-serialization, should not be used otherwise.
*/
public void setUnscaledValue(UnsignedInt128 unscaledValue) {
this.unscaledValue = unscaledValue;
}
/**
* This setter is only for de-serialization, should not be used otherwise.
*/
public void setScale(short scale) {
this.scale = scale;
}
/**
* This setter is only for de-serialization, should not be used otherwise.
*/
public void setSignum(byte signum) {
this.signum = signum;
}
}