package de.invesdwin.util.math.decimal.internal.impl; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Locale; import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.math3.dfp.Dfp; import de.invesdwin.util.assertions.Assertions; import de.invesdwin.util.error.UnknownArgumentException; import de.invesdwin.util.lang.Strings; import de.invesdwin.util.math.decimal.ADecimal; import de.invesdwin.util.math.decimal.Decimal; @ThreadSafe public class DoubleDecimalImpl extends ADecimalImpl<DoubleDecimalImpl, Double> { private static final Double FIRST_ABOVE_ZERO = 0.000000001; private static final Double FIRST_BELOW_ZERO = -0.000000001; private static final Double ZERO = 0d; private static final Double NEGATIVE_ZERO = -0d; private static final DoubleDecimalImpl ZERO_IMPL = new DoubleDecimalImpl(ZERO, ZERO); static { //ensure rounding performance fix uses correct scale final NumberFormat df = new DecimalFormat("0.##########################"); Assertions.assertThat(Strings.countMatches(df.format(FIRST_ABOVE_ZERO), "0")) .isEqualTo(ADecimal.DEFAULT_ROUNDING_SCALE); Assertions.assertThat(Strings.countMatches(df.format(FIRST_BELOW_ZERO), "0")) .isEqualTo(ADecimal.DEFAULT_ROUNDING_SCALE); } public DoubleDecimalImpl(final Double value, final Double defaultRoundedValue) { super(value, defaultRoundedValue); final double doubleValue = getValue(); if (Double.isNaN(doubleValue)) { throw new IllegalArgumentException("NaN: " + doubleValue); } if (Double.isInfinite(doubleValue)) { throw new IllegalArgumentException("Infinite: " + doubleValue); } } @Override public boolean isZero() { return internalCompareTo(ZERO_IMPL) == 0; } @Override public boolean isPositive() { return getDefaultRoundedValue() >= ZERO; } @Override public String internalToString() { final NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH); format.setMaximumFractionDigits(MathContext.DECIMAL128.getPrecision()); format.setRoundingMode(Decimal.DEFAULT_ROUNDING_MODE); format.setGroupingUsed(false); return format.format(getDefaultRoundedValue()); } @Override protected int internalCompareTo(final ADecimal<?> decimalOther) { //improve compare performance by rounding less often final DoubleDecimalImpl doubleDecimalOther = (DoubleDecimalImpl) decimalOther.getImpl(); return internalCompareTo(doubleDecimalOther); } private int internalCompareTo(final DoubleDecimalImpl doubleDecimalOther) { final Double doubleOther = doubleDecimalOther.getValue(); final Double doubleThis = getValue(); final Double difference = doubleThis - doubleOther; if (difference > FIRST_ABOVE_ZERO) { return 1; } else if (difference < FIRST_BELOW_ZERO) { return -1; } else if (ZERO.equals(difference) || NEGATIVE_ZERO.equals(difference)) { return 0; } else { final double roundedOther = doubleDecimalOther.getDefaultRoundedValue(); return getDefaultRoundedValue().compareTo(roundedOther); } } @Override public DoubleDecimalImpl scaleByPowerOfTen(final int n) { return multiply(Math.pow(10, n)); } @Override public Number numberValue() { return getDefaultRoundedValue(); } @Override public double doubleValue() { return getDefaultRoundedValue().doubleValue(); } @Override public double doubleValueRaw() { return getValue(); } @Override public float floatValue() { return getDefaultRoundedValue().floatValue(); } @Override public int intValue() { return getDefaultRoundedValue().intValue(); } @Override public long longValue() { return getDefaultRoundedValue().longValue(); } @Override public byte byteValue() { return getDefaultRoundedValue().byteValue(); } @Override public short shortValue() { return getDefaultRoundedValue().shortValue(); } @Override public DoubleDecimalImpl abs() { return newValueCopy(Math.abs(getValue())); } @Override public DoubleDecimalImpl root(final ADecimal<?> n) { return root(n.doubleValueRaw()); } @Override public DoubleDecimalImpl root(final Number n) { final double log = Math.log(getValue()); final double result = Math.exp(log / n.doubleValue()); return newValueCopy(result); } @Override public DoubleDecimalImpl sqrt() { return newValueCopy(Math.sqrt(getValue())); } @Override public DoubleDecimalImpl pow(final Number exponent) { final double a = getValue(); final double b = exponent.doubleValue(); double pow = Math.pow(a, b); if (Double.isNaN(pow) && a < 0D) { final double absA = Math.abs(a); pow = -Math.pow(absA, b); } return newValueCopy(pow); } @Override public DoubleDecimalImpl pow(final ADecimal<?> exponent) { return pow(exponent.doubleValueRaw()); } @Override public DoubleDecimalImpl subtract(final ADecimal<?> subtrahend) { final double value = getValue() - subtrahend.doubleValueRaw(); return newValueCopy(value, value); } @Override public DoubleDecimalImpl add(final ADecimal<?> augend) { final double value = getValue() + augend.doubleValueRaw(); return newValueCopy(value, value); } @Override public DoubleDecimalImpl multiply(final ADecimal<?> multiplicant) { return newValueCopy(getValue() * multiplicant.doubleValueRaw()); } @Override public DoubleDecimalImpl multiply(final Number multiplicant) { return newValueCopy(getValue() * multiplicant.doubleValue()); } @Override public DoubleDecimalImpl divide(final ADecimal<?> divisor) { return newValueCopy(getValue() / divisor.doubleValueRaw()); } @Override public DoubleDecimalImpl divide(final Number divisor) { return newValueCopy(getValue() / divisor.doubleValue()); } @Override public DoubleDecimalImpl remainder(final ADecimal<?> divisor) { return newValueCopy(getValue() % divisor.doubleValueRaw()); } @Override public DoubleDecimalImpl remainder(final Number divisor) { return newValueCopy(getValue() % divisor.doubleValue()); } @Override public DoubleDecimalImpl log() { return newValueCopy(Math.log(getValue())); } @Override public DoubleDecimalImpl exp() { return newValueCopy(Math.exp(getValue())); } @Override public DoubleDecimalImpl log10() { return newValueCopy(Math.log10(getValue())); } @Override public DoubleDecimalImpl exp10() { return newValueCopy(Math.pow(10D, getValue())); } @Override public DoubleDecimalImpl cos() { return newValueCopy(Math.cos(getValue())); } @Override public DoubleDecimalImpl sin() { return newValueCopy(Math.sin(getValue())); } @Override public BigDecimal bigDecimalValue() { return BigDecimalDecimalImplFactory.toBigDecimal(getValue()); } @Override public BigInteger bigIntegerValue() { return bigDecimalValue().toBigInteger(); } @Override public Dfp dfpValue() { return DfpDecimalImplFactory.toDfp(getDefaultRoundedValue()); } @Override protected Double internalRound(final Double value, final int scale, final RoundingMode roundingMode) { final long factor = (long) Math.pow(10, scale); final double toBeRoundedValue; if (scale < Decimal.DEFAULT_ROUNDING_SCALE && roundingMode != Decimal.DEFAULT_ROUNDING_MODE) { //fix 1 represented as 0.9999999 becoming 0 here instead of correctly being 1; for instance in FLOOR rounding mode toBeRoundedValue = internalRound(value, scale + Decimal.DEFAULT_ROUNDING_SCALE, Decimal.DEFAULT_ROUNDING_MODE) * factor; } else { toBeRoundedValue = value * factor; } final double roundedValue; switch (roundingMode) { case CEILING: roundedValue = Math.ceil(toBeRoundedValue); break; case UP: if (toBeRoundedValue >= 0) { roundedValue = (long) (toBeRoundedValue + 1d); } else { roundedValue = (long) (toBeRoundedValue - 1d); } break; case FLOOR: roundedValue = Math.floor(toBeRoundedValue); break; case DOWN: roundedValue = (long) toBeRoundedValue; break; case HALF_DOWN: if (toBeRoundedValue >= 0) { roundedValue = Math.ceil(toBeRoundedValue - 0.5d); } else { roundedValue = Math.floor(toBeRoundedValue + 0.5d); } break; case HALF_EVEN: //if the value is even and the fraction is 0.5, we need to round to the even number final long longValue = (long) toBeRoundedValue; if (longValue % 2 == 0) { //need to rounded here, since 0.5 can not be represented properly for doubles final long firstFractionalDigit = Math.abs(Math.round(toBeRoundedValue % 1 * 10)); if (firstFractionalDigit == 5) { roundedValue = longValue; break; } } //otherwise round to the nearest number roundedValue = Math.rint(toBeRoundedValue); break; case HALF_UP: if (toBeRoundedValue >= 0) { roundedValue = Math.floor(toBeRoundedValue + 0.5d); } else { roundedValue = Math.ceil(toBeRoundedValue - 0.5); } break; default: throw UnknownArgumentException.newInstance(RoundingMode.class, roundingMode); } return roundedValue / factor; } @Override protected Double getZero() { return ZERO; } @Override protected DoubleDecimalImpl newValueCopy(final Double value, final Double defaultRoundedValue) { return new DoubleDecimalImpl(value, defaultRoundedValue); } @Override protected DoubleDecimalImpl getGenericThis() { return this; } }