com.amazon.ion.Decimal Maven / Gradle / Ivy
Show all versions of ion-java Show documentation
/*
* Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazon.ion;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
/**
* An extension of {@link BigDecimal} that can represent negative zeros.
* The primary change is the addition of {@link #isNegativeZero()}.
*
* WARNING: This class should not be extended by code outside of
* this library.
*
* This class currently does not have any behavioral difference
* from {@link BigDecimal} except for the following:
*
* - {@link #toString()}, {@link #toEngineeringString()}, and
* {@link #toPlainString()} print a negative sign if necessary.
*
* - {@link #floatValue()} and {@link #doubleValue()} return negative zero
* results.
*
* - {@link #abs} does the right thing.
*
* This class does NOT override {@link #equals} or {@link #compareTo}
* so those methods cannot distinguish positive and negative zeros.
*
* It also does not currently override any of the numerical methods,
* but it may do so in the future. If you are concerned about
* consistent treatment of negative zeros in future releases, you may wish to
* use {@link #bigDecimalValue} before performing those operations.
*/
public class Decimal
extends BigDecimal
{
private static final long serialVersionUID = 1L;
private static final class NegativeZero extends Decimal
{
private static final long serialVersionUID = 1L;
private NegativeZero(int scale)
{
super(BigInteger.ZERO, scale);
}
private NegativeZero(int scale, MathContext mc)
{
super(BigInteger.ZERO, scale, mc);
}
@Override
public float floatValue()
{
float v = super.floatValue();
if (Float.compare(0f, v) <= 0) v = -1 * v;
return v;
}
@Override
public double doubleValue()
{
double v = super.doubleValue();
if (Double.compare(0d, v) <= 0) v = -1 * v;
return v;
}
@Override
public BigDecimal abs()
{
return new BigDecimal(unscaledValue(), scale());
}
@Override
public BigDecimal abs(MathContext mc)
{
return new BigDecimal(unscaledValue(), scale(), mc);
}
// TODO signum might break clients if -0 returns <0 instead of 0
// TODO static negate(BigDecimal)
// TODO hashcode
// TODO some other things:
// * byte/int/longValueExact throws?
// * movePointLeft/Right
// * round
// * scaleByPowerOfTen
// * setScale
// * stripTrailingZeros
// * toBigIntegerExact
@Override
public String toString()
{
return '-' + super.toString();
}
@Override
public String toEngineeringString()
{
return '-' + super.toEngineeringString();
}
@Override
public String toPlainString()
{
return '-' + super.toPlainString();
}
}
/**
* The value 0, with a scale of 0.
*/
public static final Decimal ZERO = new Decimal(0);
/**
* The value -0, with a scale of 0.
*/
public static final Decimal NEGATIVE_ZERO = new NegativeZero(0);
/**
* Efficiently determines whether an arbitary decimal value is a negative
* zero. This can only be true when the value is actually a
* {@link Decimal}.
*
* @return {@code true} if and only if the value is a negative zero.
*
* @throws NullPointerException if the value is {@code null}.
*/
public static boolean isNegativeZero(BigDecimal val)
{
return (val.getClass() == NegativeZero.class);
}
/**
* Returns a "plain" {@link BigDecimal} instance, never a {@link Decimal}
* subclass. As a side effect, this strips any negative-zero information.
*
* @param val may be null.
* @return {@code null} if the given value is {@code null}.
*/
public static BigDecimal bigDecimalValue(BigDecimal val)
{
if (val == null
|| val.getClass() == BigDecimal.class)
{
return val;
}
return new BigDecimal(val.unscaledValue(), val.scale());
}
/**
* Compares two decimal values for equality, observing both precision and
* negative zeros. This differs from {@link BigDecimal#equals(Object)},
* which isn't aware of negative zeros, and from
* {@link BigDecimal#compareTo(BigDecimal)}, which ignores both precision
* and negative zeros.
*
* @return {@code true} if and only if the two {@link BigDecimal} objects
* have the same value, scale, and sign.
*
* @throws NullPointerException if either parameter is {@code null}.
*/
public static boolean equals(BigDecimal v1, BigDecimal v2)
{
return (isNegativeZero(v1) == isNegativeZero(v2)
&& v1.equals(v2));
}
//=========================================================================
/**
* Returns a negative-zero decimal value, with the given number of
* significant digits (zeros).
*
* @param scale the number of significant digits (zeros) after the decimal
* point.
*/
public static Decimal negativeZero(int scale)
{
return new NegativeZero(scale);
}
/**
* Returns a negative-zero decimal value, with the given number of
* significant digits (zeros) and given context.
*
* @param scale the number of significant digits (zeros) after the decimal
* point.
*/
public static Decimal negativeZero(int scale, MathContext mc)
{
return new NegativeZero(scale, mc);
}
public static Decimal valueOf(BigInteger unscaledVal, int scale)
{
return new Decimal(unscaledVal, scale);
}
public static Decimal valueOf(BigInteger unscaledVal, int scale,
MathContext mc)
{
return new Decimal(unscaledVal, scale, mc);
}
public static Decimal valueOf(BigInteger val)
{
return new Decimal(val);
}
public static Decimal valueOf(BigInteger val, MathContext mc)
{
return new Decimal(val, mc);
}
public static Decimal valueOf(int val)
{
return new Decimal(val);
}
public static Decimal valueOf(int val, MathContext mc)
{
return new Decimal(val, mc);
}
public static Decimal valueOf(long val)
{
return new Decimal(val);
}
public static Decimal valueOf(long val, MathContext mc)
{
return new Decimal(val, mc);
}
public static Decimal valueOf(double val)
{
if (Double.compare(val, -0d) == 0)
{
// Simulate BigDecimal.valueOf(0d) which has scale of 1
return new NegativeZero(1);
}
return new Decimal(Double.toString(val));
}
public static Decimal valueOf(double val, MathContext mc)
{
if (Double.compare(val, -0d) == 0)
{
return new NegativeZero(1, mc);
}
return new Decimal(Double.toString(val), mc);
}
public static Decimal valueOf(BigDecimal val)
{
if (val == null || val instanceof Decimal) return (Decimal) val;
return new Decimal(val.unscaledValue(), val.scale());
}
public static Decimal valueOf(BigDecimal val, MathContext mc)
{
return new Decimal(val.unscaledValue(), val.scale(), mc);
}
public static Decimal valueOf(String val)
{
boolean negative = val.startsWith("-");
Decimal ibd = new Decimal(val);
if (negative && ibd.signum() == 0)
{
ibd = new NegativeZero(ibd.scale());
}
return ibd;
}
public static Decimal valueOf(String val, MathContext mc)
{
boolean negative = val.startsWith("-");
Decimal ibd = new Decimal(val, mc);
if (negative && ibd.signum() == 0)
{
ibd = new NegativeZero(ibd.scale(), mc);
}
return ibd;
}
//=========================================================================
// Constructors are private so we have flexibility in changing
// implementation of how we create negative zero.
// We force the user to call static valueOf() methods instead.
private Decimal(BigInteger unscaledVal, int scale)
{
super(unscaledVal, scale);
}
private Decimal(BigInteger unscaledVal, int scale, MathContext mc)
{
super(unscaledVal, scale, mc);
}
private Decimal(BigInteger val)
{
super(val);
}
private Decimal(BigInteger val, MathContext mc)
{
super(val, mc);
}
private Decimal(int val)
{
super(val);
}
private Decimal(int val, MathContext mc)
{
super(val, mc);
}
private Decimal(long val)
{
super(val);
}
private Decimal(long val, MathContext mc)
{
super(val, mc);
}
private Decimal(double val)
{
super(val);
}
private Decimal(double val, MathContext mc)
{
super(val, mc);
}
// TODO create static valueOf to check for -0
private Decimal(char[] in, int offset, int len)
{
super(in, offset, len);
}
// TODO create static valueOf to check for -0
private Decimal(char[] in, int offset, int len, MathContext mc)
{
super(in, offset, len, mc);
}
// TODO create static valueOf to check for -0
private Decimal(char[] in)
{
super(in);
}
// TODO create static valueOf to check for -0
private Decimal(char[] in, MathContext mc)
{
super(in, mc);
}
private Decimal(String val)
{
super(val);
}
private Decimal(String val, MathContext mc)
{
super(val, mc);
}
//========================================================================
public final boolean isNegativeZero()
{
return (getClass() == NegativeZero.class);
}
/**
* Converts this to a "plain" {@link BigDecimal} instance, losing any
* negative zeros in the process.
*
* @return a {@link BigDecimal}, never a {@link Decimal}.
*/
public final BigDecimal bigDecimalValue()
{
return new BigDecimal(unscaledValue(), scale());
}
}