package de.invesdwin.util.math.decimal; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import javax.annotation.concurrent.Immutable; import org.apache.commons.math3.dfp.Dfp; import org.assertj.core.description.TextDescription; import de.invesdwin.norva.marker.IDecimal; import de.invesdwin.util.lang.ADelegateComparator; import de.invesdwin.util.lang.Objects; import de.invesdwin.util.math.decimal.internal.impl.ADecimalImpl; @Immutable public abstract class ADecimal<E extends ADecimal<E>> extends Number implements Comparable<Object>, IDecimal { public static final String NEGATIVE_SIGN = "-"; public static final String POSITIVE_SIGN = "+"; /** * Using HALF_UP here for commercial rounding. */ public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP; public static final int DEFAULT_ROUNDING_SCALE = 9; public static final ADelegateComparator<ADecimal<?>> COMPARATOR = new ADelegateComparator<ADecimal<?>>() { @Override protected Comparable<?> getCompareCriteria(final ADecimal<?> e) { return e; } }; protected transient Boolean isZero; protected transient Boolean isPositive; public abstract ADecimalImpl getImpl(); /** * http://stackoverflow.com/questions/2170872/does-java-casting-introduce-overhead-why */ protected abstract E getGenericThis(); protected abstract E newValueCopy(ADecimalImpl value); public abstract E fromDefaultValue(Decimal value); public List<E> fromDefaultValue(final List<Decimal> values) { final List<E> converted = new ArrayList<E>(values.size()); for (final Decimal value : values) { converted.add(fromDefaultValue(value)); } return converted; } @SuppressWarnings("unchecked") public E[] fromDefaultValue(final Decimal[] values) { final E[] converted = (E[]) Array.newInstance(getGenericThis().getClass(), values.length); return fromDefaultValue(values, converted); } public E[] fromDefaultValue(final Decimal[] values, final E[] destination) { for (int i = 0; i < values.length; i++) { destination[i] = fromDefaultValue(values[i]); } return destination; } public abstract Decimal getDefaultValue(); @Override public int hashCode() { return Objects.hashCode(getClass(), getImpl()); } @Override public int compareTo(final Object other) { return getImpl().compareTo(other); } @Override public boolean equals(final Object other) { return other != null && getClass().isAssignableFrom(other.getClass()) && Objects.equals(getImpl(), other); } @Override public String toString() { return getImpl().toString(); } @Override public int intValue() { return getImpl().intValue(); } @Override public long longValue() { return getImpl().longValue(); } @Override public float floatValue() { return getImpl().floatValue(); } @Override public double doubleValue() { return getImpl().doubleValue(); } /** * This gives the raw value, thus not getting rounded for precision. */ public double doubleValueRaw() { return getImpl().doubleValueRaw(); } @Override public byte byteValue() { return getImpl().byteValue(); } @Override public short shortValue() { return getImpl().shortValue(); } public BigDecimal bigDecimalValue() { return getImpl().bigDecimalValue(); } public BigInteger bigIntegerValue() { return getImpl().bigIntegerValue(); } public Dfp dfpValue() { return getImpl().dfpValue(); } public Number numberValue() { return getImpl().numberValue(); } /** * This value denotes how many digits are after the decimal point. E.g.: 12.0001 results in 4 * * Also called decimal scale. * * Returns the real scale without trailing zeros. * * WARNING: make sure this values gets cached, because this operation causes a large performance overhead! */ public int getDecimalDigits() { return getImpl().getDecimalDigits(); } /** * This value denotes how many digits are before the decimal point. E.g.: 12.0001 results in 2 * * Also called decimal precision minus decimal scale. * * Returns the real scale without trailing zeros. * * WARNING: make sure this values gets cached, because this operation causes a large performance overhead! */ public int getWholeNumberDigits() { return getImpl().getWholeNumberDigits(); } /** * This value denotes how many digits are after the decimal point. E.g.: 12.0001 results in 6 * * Also called decimal precision. * * Returns the real precision without trailing zeros. * * WARNING: make sure this values gets cached, because this operation causes a large performance overhead! */ public int getDigits() { return getImpl().getDigits(); } public boolean isZero() { if (isZero == null) { isZero = getImpl().isZero(); } return isZero; } public final boolean isNotZero() { return !isZero(); } /** * 0 is counted as positive as well here to make things simpler. */ public boolean isPositive() { if (isPositive == null) { isPositive = getImpl().isPositive(); } return isPositive; } /** * This one excludes 0 from positive. */ public boolean isPositiveNonZero() { return isPositive() && !isZero(); } public boolean isNegative() { return !isPositive(); } public boolean isNegativeOrZero() { return !isPositiveNonZero(); } public boolean isGreaterThan(final Number o) { if (o == null) { return false; } return compareTo(o) > 0; } public boolean isGreaterThanOrEqualTo(final Number o) { return !isLessThan(o); } public boolean isLessThan(final Number o) { if (o == null) { return false; } return compareTo(o) < 0; } public boolean isLessThanOrEqualTo(final Number o) { return !isGreaterThan(o); } public boolean isBetween(final Number lowerBound, final Number upperBound) { return isGreaterThanOrEqualTo(lowerBound) && isLessThanOrEqualTo(upperBound); } public E scaleByPowerOfTen(final int n) { return newValueCopy(getImpl().scaleByPowerOfTen(n)); } public E round() { return round(DEFAULT_ROUNDING_SCALE); } public E round(final RoundingMode roundingMode) { return round(DEFAULT_ROUNDING_SCALE, roundingMode); } public E round(final int scale) { return round(scale, DEFAULT_ROUNDING_MODE); } public E round(final int scale, final RoundingMode roundingMode) { return newValueCopy(getImpl().round(scale, roundingMode)); } /** * <ul> * <li>[growth rate] = ( [current] - [previous] ) / |[previous]|</li> * <ul> * * @see <a href="http://www.chemieonline.de/forum/showthread.php?t=101560">Source</a> */ public E growthRate(final ADecimal<E> nextValue) { return nextValue.subtract(getGenericThis()).divide(abs()); } public E abs() { return newValueCopy(getImpl().abs()); } /** * Returns the natural logarithm. */ public E log() { if (isNegativeOrZero()) { return zero(); } return newValueCopy(getImpl().log()); } /** * Returns the logarithm base 10. */ public E log10() { if (isNegativeOrZero()) { return zero(); } return newValueCopy(getImpl().log10()); } /** * Returns Euler's number <i>e</i> raised to the power of this value. */ public E exp() { return newValueCopy(getImpl().exp()); } public E cos() { return newValueCopy(getImpl().cos()); } public E sin() { return newValueCopy(getImpl().sin()); } /** * Returns 10 raised to the power of this value. */ public E exp10() { return newValueCopy(getImpl().exp10()); } public E subtract(final ADecimal<E> subtrahend) { if (subtrahend == null) { return getGenericThis(); } return newValueCopy(getImpl().subtract(subtrahend)); } public E add(final ADecimal<E> augend) { if (augend == null) { return getGenericThis(); } return newValueCopy(getImpl().add(augend)); } public E multiply(final ADecimal<E> multiplicant) { if (isZero()) { return getGenericThis(); } else if (multiplicant == null) { return multiply(0); } else { return newValueCopy(getImpl().multiply(multiplicant)); } } /** * returns the remainder of the division. */ public E remainder(final ADecimal<E> divisor) { if (isZero()) { return getGenericThis(); } else if (divisor == null || divisor.isZero()) { return remainder(0); } else { return newValueCopy(getImpl().remainder(divisor)); } } public E remainder(final Number divisor) { if (isZero()) { return getGenericThis(); } else if (divisor == null || divisor.doubleValue() == 0D) { //results in 0, thus multiply by 0 return newValueCopy(getImpl().remainder(0)); } else { if (divisor instanceof AScaledDecimal) { throw new IllegalArgumentException(new TextDescription( "Division between different types of %ss [%s=%s / %s=%s] does not make any sense. Please be more specific.", AScaledDecimal.class.getSimpleName(), this.getClass().getSimpleName(), this, divisor.getClass().getSimpleName(), divisor).toString()); } return newValueCopy(getImpl().remainder(divisor)); } } public E multiply(final Number multiplicant) { if (isZero()) { return getGenericThis(); } else if (multiplicant == null) { return newValueCopy(getImpl().multiply(0)); } else { if (multiplicant instanceof AScaledDecimal) { throw new IllegalArgumentException(new TextDescription( "Multiplication between different types of %ss [%s=%s * %s=%s] does not make any sense. Please be more specific.", AScaledDecimal.class.getSimpleName(), this.getClass().getSimpleName(), this, multiplicant.getClass().getSimpleName(), multiplicant).toString()); } return newValueCopy(getImpl().multiply(multiplicant)); } } /** * If the divisor is 0, 0 is returned. This goes against the mathematical rules, but makes a developers life easier. */ public E divide(final ADecimal<E> divisor) { if (isZero()) { //prevent NaN return getGenericThis(); } else if (divisor == null || divisor.isZero()) { return divide(0); } else { return newValueCopy(getImpl().divide(divisor)); } } /** * If the divisor is 0, 0 is returned. This goes against the mathematical rules, but makes a developers life easier. */ public E divide(final Number divisor) { if (isZero()) { //prevent NaN return getGenericThis(); } else if (divisor == null || divisor.doubleValue() == 0D) { //results in 0, thus multiply by 0 return newValueCopy(getImpl().multiply(0)); } else { if (divisor instanceof AScaledDecimal) { throw new IllegalArgumentException(new TextDescription( "Division between different types of %ss [%s=%s / %s=%s] does not make any sense. Please be more specific.", AScaledDecimal.class.getSimpleName(), this.getClass().getSimpleName(), this, divisor.getClass().getSimpleName(), divisor).toString()); } return newValueCopy(getImpl().divide(divisor)); } } /** * With a step of 0.5: (Math.ceil(x * 2) / 2) */ public E roundToStep(final ADecimal<E> step) { return roundToStep(step, DEFAULT_ROUNDING_MODE); } public E roundToStep(final ADecimal<E> step, final RoundingMode roundingMode) { final E stepReciprocal = step.reciprocal(); return multiply(stepReciprocal).round(0, roundingMode).divide(stepReciprocal); } public E reciprocal() { return newValueCopy(Decimal.ONE.getImpl()).divide(getGenericThis()); } public E orHigher(final E other) { if (other == null) { return getGenericThis(); } if (compareTo(other) > 0) { return getGenericThis(); } else { return other; } } public E orLower(final E other) { if (other == null) { return getGenericThis(); } if (compareTo(other) < 0) { return getGenericThis(); } else { return other; } } public E between(final E min, final E max) { if (min.isGreaterThan(max)) { throw new IllegalArgumentException("min [" + min + "] must not be larger than max [" + max + "]"); } return orLower(max).orHigher(min); } public E pow(final ADecimal<E> exponent) { return newValueCopy(getImpl().pow(exponent)); } public E pow(final Number exponent) { return newValueCopy(getImpl().pow(exponent)); } public E sqrt() { return newValueCopy(getImpl().sqrt()); } /** * Root = Value^1/n * * @see <a href="http://www.ee.ucl.ac.uk/~mflanaga/java/Stat.html#geom2">Source with BigDecimal</a> */ public E root(final Number n) { return newValueCopy(getImpl().root(n)); } public E distance(final ADecimal<E> to) { return subtract(to).abs(); } public E negate() { return multiply(-1); } public String getSign() { return getSign(false); } public String getSignInverted() { return getSign(true); } public String getSign(final boolean inverted) { if (isPositive()) { if (!inverted) { return POSITIVE_SIGN; } else { return NEGATIVE_SIGN; } } else { if (!inverted) { return NEGATIVE_SIGN; } else { return POSITIVE_SIGN; } } } public abstract E zero(); public static <T extends ADecimal<T>> T zeroToNull(final T value) { if (value == null || value.isZero()) { return null; } else { return value; } } public static <T extends ADecimal<T>> T sum(final T value1, final T value2) { if (value1 == null) { return value2; } else { return value1.add(value2); } } public static <T extends ADecimal<T>> T max(final T value1, final T value2) { if (value1 == null) { return value2; } else { return value1.orHigher(value2); } } public static <T extends ADecimal<T>> T min(final T value1, final T value2) { if (value1 == null) { return value2; } else { return value1.orLower(value2); } } public static <T extends ADecimal<T>> T negate(final T value) { if (value == null) { return null; } else { return value.negate(); } } public static <T extends ADecimal<T>> T abs(final T value) { if (value == null) { return null; } else { return value.abs(); } } public abstract String toFormattedString(); public abstract String toFormattedString(final String format); }