org.bson.types.Decimal128 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongo-java-driver Show documentation
Show all versions of mongo-java-driver Show documentation
The MongoDB Java Driver uber-artifact, containing the legacy driver, the mongodb-driver, mongodb-driver-core, and bson
/*
* Copyright 2008-present MongoDB, Inc.
*
* 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.bson.types;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;
import static java.math.MathContext.DECIMAL128;
import static java.util.Arrays.asList;
/**
* A binary integer decimal representation of a 128-bit decimal value, supporting 34 decimal digits of significand and an exponent range
* of -6143 to +6144.
*
* @since 3.4
* @see BSON Decimal128
* specification
* @see binary integer decimal
* @see decimal128 floating-point format
* @see 754-2008 - IEEE Standard for Floating-Point Arithmetic
*/
public final class Decimal128 implements Serializable {
private static final long serialVersionUID = 4570973266503637887L;
private static final long INFINITY_MASK = 0x7800000000000000L;
private static final long NaN_MASK = 0x7c00000000000000L;
private static final long SIGN_BIT_MASK = 1L << 63;
private static final int MIN_EXPONENT = -6176;
private static final int MAX_EXPONENT = 6111;
private static final int EXPONENT_OFFSET = 6176;
private static final int MAX_BIT_LENGTH = 113;
private static final BigInteger BIG_INT_TEN = new BigInteger("10");
private static final BigInteger BIG_INT_ONE = new BigInteger("1");
private static final BigInteger BIG_INT_ZERO = new BigInteger("0");
private static final Set NaN_STRINGS = new HashSet(asList("nan"));
private static final Set NEGATIVE_NaN_STRINGS = new HashSet(asList("-nan"));
private static final Set POSITIVE_INFINITY_STRINGS = new HashSet(asList("inf", "+inf", "infinity", "+infinity"));
private static final Set NEGATIVE_INFINITY_STRINGS = new HashSet(asList("-inf", "-infinity"));
/**
* A constant holding the positive infinity of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("Infinity")}.
*/
public static final Decimal128 POSITIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK, 0);
/**
* A constant holding the negative infinity of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("-Infinity")}.
*/
public static final Decimal128 NEGATIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK | SIGN_BIT_MASK, 0);
/**
* A constant holding a negative Not-a-Number (-NaN) value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("-NaN")}.
*/
public static final Decimal128 NEGATIVE_NaN = fromIEEE754BIDEncoding(NaN_MASK | SIGN_BIT_MASK, 0);
/**
* A constant holding a Not-a-Number (NaN) value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("NaN")}.
*/
public static final Decimal128 NaN = fromIEEE754BIDEncoding(NaN_MASK, 0);
/**
* A constant holding a postive zero value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("0")}.
*/
public static final Decimal128 POSITIVE_ZERO = fromIEEE754BIDEncoding(0x3040000000000000L, 0x0000000000000000L);
/**
* A constant holding a negative zero value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("-0")}.
*/
public static final Decimal128 NEGATIVE_ZERO = fromIEEE754BIDEncoding(0xb040000000000000L, 0x0000000000000000L);
private final long high;
private final long low;
/**
* Returns a Decimal128 value representing the given String.
*
* @param value the Decimal128 value represented as a String
* @return the Decimal128 value representing the given String
* @throws NumberFormatException if the value is out of the Decimal128 range
* @see
*
* From-String Specification
*/
public static Decimal128 parse(final String value) {
String lowerCasedValue = value.toLowerCase();
if (NaN_STRINGS.contains(lowerCasedValue)) {
return NaN;
}
if (NEGATIVE_NaN_STRINGS.contains(lowerCasedValue)) {
return NEGATIVE_NaN;
}
if (POSITIVE_INFINITY_STRINGS.contains(lowerCasedValue)) {
return POSITIVE_INFINITY;
}
if (NEGATIVE_INFINITY_STRINGS.contains(lowerCasedValue)) {
return NEGATIVE_INFINITY;
}
return new Decimal128(new BigDecimal(value), value.charAt(0) == '-');
}
/**
* Create an instance with the given high and low order bits representing this Decimal128 as an IEEE 754-2008 128-bit decimal
* floating point using the BID encoding scheme.
*
* @param high the high-order 64 bits
* @param low the low-order 64 bits
* @return the Decimal128 value representing the given high and low order bits
*/
public static Decimal128 fromIEEE754BIDEncoding(final long high, final long low) {
return new Decimal128(high, low);
}
/**
* Constructs a Decimal128 value representing the given long.
*
* @param value the Decimal128 value represented as a long
*/
public Decimal128(final long value) {
this(new BigDecimal(value, DECIMAL128));
}
/**
* Constructs a Decimal128 value representing the given BigDecimal.
*
* @param value the Decimal128 value represented as a BigDecimal
* @throws NumberFormatException if the value is out of the Decimal128 range
*/
public Decimal128(final BigDecimal value) {
this(value, value.signum() == -1);
}
private Decimal128(final long high, final long low) {
this.high = high;
this.low = low;
}
// isNegative is necessary to detect -0, which can't be represented with a BigDecimal
private Decimal128(final BigDecimal initialValue, final boolean isNegative) {
long localHigh = 0;
long localLow = 0;
BigDecimal value = clampAndRound(initialValue);
long exponent = -value.scale();
if ((exponent < MIN_EXPONENT) || (exponent > MAX_EXPONENT)) {
throw new AssertionError("Exponent is out of range for Decimal128 encoding: " + exponent); }
if (value.unscaledValue().bitLength() > MAX_BIT_LENGTH) {
throw new AssertionError("Unscaled roundedValue is out of range for Decimal128 encoding:" + value.unscaledValue());
}
BigInteger significand = value.unscaledValue().abs();
int bitLength = significand.bitLength();
for (int i = 0; i < Math.min(64, bitLength); i++) {
if (significand.testBit(i)) {
localLow |= 1L << i;
}
}
for (int i = 64; i < bitLength; i++) {
if (significand.testBit(i)) {
localHigh |= 1L << (i - 64);
}
}
long biasedExponent = exponent + EXPONENT_OFFSET;
localHigh |= biasedExponent << 49;
if (value.signum() == -1 || isNegative) {
localHigh |= SIGN_BIT_MASK;
}
high = localHigh;
low = localLow;
}
private BigDecimal clampAndRound(final BigDecimal initialValue) {
BigDecimal value;
if (-initialValue.scale() > MAX_EXPONENT) {
int diff = -initialValue.scale() - MAX_EXPONENT;
if (initialValue.unscaledValue().equals(BIG_INT_ZERO)) {
value = new BigDecimal(initialValue.unscaledValue(), -MAX_EXPONENT);
} else if (diff + initialValue.precision() > 34) {
throw new NumberFormatException("Exponent is out of range for Decimal128 encoding of " + initialValue);
} else {
BigInteger multiplier = BIG_INT_TEN.pow(diff);
value = new BigDecimal(initialValue.unscaledValue().multiply(multiplier), initialValue.scale() + diff);
}
} else if (-initialValue.scale() < MIN_EXPONENT) {
// Increasing a very negative exponent may require decreasing precision, which is rounding
// Only round exactly (by removing precision that is all zeroes). An exception is thrown if the rounding would be inexact:
// Exact: .000...0011000 => 11000E-6177 => 1100E-6176 => .000001100
// Inexact: .000...0011001 => 11001E-6177 => 1100E-6176 => .000001100
int diff = initialValue.scale() + MIN_EXPONENT;
int undiscardedPrecision = ensureExactRounding(initialValue, diff);
BigInteger divisor = undiscardedPrecision == 0 ? BIG_INT_ONE : BIG_INT_TEN.pow(diff);
value = new BigDecimal(initialValue.unscaledValue().divide(divisor), initialValue.scale() - diff);
} else {
value = initialValue.round(DECIMAL128);
int extraPrecision = initialValue.precision() - value.precision();
if (extraPrecision > 0) {
// Again, only round exactly
ensureExactRounding(initialValue, extraPrecision);
}
}
return value;
}
private int ensureExactRounding(final BigDecimal initialValue, final int extraPrecision) {
String significand = initialValue.unscaledValue().abs().toString();
int undiscardedPrecision = Math.max(0, significand.length() - extraPrecision);
for (int i = undiscardedPrecision; i < significand.length(); i++) {
if (significand.charAt(i) != '0') {
throw new NumberFormatException("Conversion to Decimal128 would require inexact rounding of " + initialValue);
}
}
return undiscardedPrecision;
}
/**
* Gets the high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding
* scheme.
*
* @return the high-order 64 bits of this Decimal128
*/
public long getHigh() {
return high;
}
/**
* Gets the low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding
* scheme.
*
* @return the low-order 64 bits of this Decimal128
*/
public long getLow() {
return low;
}
/**
* Gets a BigDecimal that is equivalent to this Decimal128.
*
* @return a BigDecimal that is equivalent to this Decimal128
* @throws ArithmeticException if the Decimal128 value is NaN, Infinity, -Infinity, or -0, none of which can be represented as a
* BigDecimal
*/
public BigDecimal bigDecimalValue() {
if (isNaN()) {
throw new ArithmeticException("NaN can not be converted to a BigDecimal");
}
if (isInfinite()) {
throw new ArithmeticException("Infinity can not be converted to a BigDecimal");
}
BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck();
// If the BigDecimal is 0, but the Decimal128 is negative, that means we have -0.
if (isNegative() && bigDecimal.signum() == 0) {
throw new ArithmeticException("Negative zero can not be converted to a BigDecimal");
}
return bigDecimal;
}
private BigDecimal bigDecimalValueNoNegativeZeroCheck() {
int scale = -getExponent();
if (twoHighestCombinationBitsAreSet()) {
return BigDecimal.valueOf(0, scale);
}
return new BigDecimal(new BigInteger(isNegative() ? -1 : 1, getBytes()), scale);
}
// May have leading zeros. Strip them before considering making this method public
private byte[] getBytes() {
byte[] bytes = new byte[15];
long mask = 0x00000000000000ff;
for (int i = 14; i >= 7; i--) {
bytes[i] = (byte) ((low & mask) >>> ((14 - i) << 3));
mask = mask << 8;
}
mask = 0x00000000000000ff;
for (int i = 6; i >= 1; i--) {
bytes[i] = (byte) ((high & mask) >>> ((6 - i) << 3));
mask = mask << 8;
}
mask = 0x0001000000000000L;
bytes[0] = (byte) ((high & mask) >>> 48);
return bytes;
}
// Consider making this method public
int getExponent() {
if (twoHighestCombinationBitsAreSet()) {
return (int) ((high & 0x1fffe00000000000L) >>> 47) - EXPONENT_OFFSET;
} else {
return (int) ((high & 0x7fff800000000000L) >>> 49) - EXPONENT_OFFSET;
}
}
private boolean twoHighestCombinationBitsAreSet() {
return (high & 3L << 61) == 3L << 61;
}
/**
* Returns true if this Decimal128 is negative.
*
* @return true if this Decimal128 is negative
*/
public boolean isNegative() {
return (high & SIGN_BIT_MASK) == SIGN_BIT_MASK;
}
/**
* Returns true if this Decimal128 is infinite.
*
* @return true if this Decimal128 is infinite
*/
public boolean isInfinite() {
return (high & INFINITY_MASK) == INFINITY_MASK;
}
/**
* Returns true if this Decimal128 is finite.
*
* @return true if this Decimal128 is finite
*/
public boolean isFinite() {
return !isInfinite();
}
/**
* Returns true if this Decimal128 is Not-A-Number (NaN).
*
* @return true if this Decimal128 is Not-A-Number
*/
public boolean isNaN() {
return (high & NaN_MASK) == NaN_MASK;
}
/**
* Returns true if the encoded representation of this instance is the same as the encoded representation of {@code o}.
*
* One consequence is that, whereas {@code Double.NaN != Double.NaN},
* {@code new Decimal128("NaN").equals(new Decimal128("NaN")} returns true.
*
*
* Another consequence is that, as with BigDecimal, {@code new Decimal128("1.0").equals(new Decimal128("1.00")} returns false,
* because the precision is not the same and therefore the representation is not the same.
*
*
* @param o the object to compare for equality
* @return true if the instances are equal
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Decimal128 that = (Decimal128) o;
if (high != that.high) {
return false;
}
if (low != that.low) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = (int) (low ^ (low >>> 32));
result = 31 * result + (int) (high ^ (high >>> 32));
return result;
}
/**
* Returns the String representation of the Decimal128 value.
*
* @return the String representation
* @see
* To-String Sprecification
*/
@Override
public String toString() {
if (isNaN()) {
return "NaN";
}
if (isInfinite()) {
if (isNegative()) {
return "-Infinity";
} else {
return "Infinity";
}
}
return toStringWithBigDecimal();
}
private String toStringWithBigDecimal() {
StringBuilder buffer = new StringBuilder();
BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck();
String significand = bigDecimal.unscaledValue().abs().toString();
if (isNegative()) {
buffer.append('-');
}
int exponent = -bigDecimal.scale();
int adjustedExponent = exponent + (significand.length() - 1);
if (exponent <= 0 && adjustedExponent >= -6) {
if (exponent == 0) {
buffer.append(significand);
} else {
int pad = -exponent - significand.length();
if (pad >= 0) {
buffer.append('0');
buffer.append('.');
for (int i = 0; i < pad; i++) {
buffer.append('0');
}
buffer.append(significand, 0, significand.length());
} else {
buffer.append(significand, 0, -pad);
buffer.append('.');
buffer.append(significand, -pad, -pad - exponent);
}
}
} else {
buffer.append(significand.charAt(0));
if (significand.length() > 1) {
buffer.append('.');
buffer.append(significand, 1, significand.length());
}
buffer.append('E');
if (adjustedExponent > 0) {
buffer.append('+');
}
buffer.append(adjustedExponent);
}
return buffer.toString();
}
}