package de.invesdwin.util.math.decimal.internal.impl; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.math3.dfp.Dfp; import de.invesdwin.norva.marker.IDecimal; import de.invesdwin.util.lang.Strings; import de.invesdwin.util.math.decimal.ADecimal; import de.invesdwin.util.math.decimal.Decimal; /** * If null values are put into this, they are automatically converted to 0. * * This class does not extend AValueObject to improve performance by skipping the bean property aspect. */ @ThreadSafe public abstract class ADecimalImpl<E extends ADecimalImpl<E, V>, V> implements Comparable<Object>, Serializable, IDecimal { private final V value; @GuardedBy("none for performance") private transient Integer wholeNumberDigits; @GuardedBy("none for performance") private transient Integer decimalDigits; @GuardedBy("none for performance") private transient Integer digits; @GuardedBy("none for performance") private transient String toString; @GuardedBy("none for performance") private transient V defaultRoundedValue; public ADecimalImpl(final V value, final V defaultRoundedValue) { if (value == null) { this.value = getZero(); this.defaultRoundedValue = this.value; } else { this.value = value; this.defaultRoundedValue = defaultRoundedValue; } } protected abstract V internalRound(V value, int scale, RoundingMode roundingMode); protected abstract V getZero(); protected final V getValue() { return value; } public int getWholeNumberDigits() { if (wholeNumberDigits == null) { /* * using string operations here because values get distorted even for BigDecimal when using * scaleByPowerOfTen */ final String s = toString(); final int indexOfDecimalPoint = s.indexOf("."); if (indexOfDecimalPoint != -1) { wholeNumberDigits = indexOfDecimalPoint; } else { wholeNumberDigits = Math.max(1, s.length()); } } return wholeNumberDigits; } /** * Returns the real scale without trailing zeros. */ public int getDecimalDigits() { if (decimalDigits == null) { /* * using string operations here because values get distorted even for BigDecimal when using * scaleByPowerOfTen */ final String s = toString(); final int indexOfDecimalPoint = s.indexOf("."); if (indexOfDecimalPoint != -1) { decimalDigits = s.length() - indexOfDecimalPoint - 1; } else { decimalDigits = 0; } } return decimalDigits; } public int getDigits() { if (digits == null) { /* * using string operations here because values get distorted even for BigDecimal when using * scaleByPowerOfTen */ final String s = toString(); final int indexOfDecimalPoint = s.indexOf("."); if (indexOfDecimalPoint != -1) { digits = s.length() - 1; } else { digits = Math.max(1, s.length()); } } return digits; } public abstract boolean isZero(); /** * 0 is counted as positive aswell here. */ public abstract boolean isPositive(); @Override public int hashCode() { return getDefaultRoundedValue().hashCode(); } @Override public boolean equals(final Object other) { return compareTo(other) == 0; } @Override public int compareTo(final Object other) { final ADecimal<?> decimalOther; if (other instanceof ADecimal) { decimalOther = (ADecimal<?>) other; } else if (other instanceof Number) { final Number cOther = (Number) other; decimalOther = Decimal.valueOf(cOther); } else { return 1; } return internalCompareTo(decimalOther); } protected abstract int internalCompareTo(ADecimal<?> other); @Override public String toString() { if (toString == null) { final String s = internalToString(); if (s.length() > 1 && s.contains(".")) { toString = Strings.removeEnd(Strings.removeTrailing(s, "0"), "."); } else { toString = s; } } return toString; } protected abstract String internalToString(); public abstract E abs(); public abstract E scaleByPowerOfTen(int n); public abstract E root(Number n); public abstract E root(ADecimal<?> n); public abstract E pow(Number exponent); public abstract E pow(ADecimal<?> exponent); public abstract E subtract(final ADecimal<?> subtrahend); public abstract E add(final ADecimal<?> augend); public abstract E multiply(final Number multiplicant); public abstract E multiply(final ADecimal<?> multiplicant); public abstract E divide(final Number divisor); public abstract E divide(final ADecimal<?> divisor); public abstract E remainder(final Number divisor); public abstract E remainder(final ADecimal<?> divisor); public E round(final int scale, final RoundingMode roundingMode) { if (roundingMode == RoundingMode.UNNECESSARY) { return getGenericThis(); } final V rounded; if (scale == Decimal.DEFAULT_ROUNDING_SCALE && roundingMode == Decimal.DEFAULT_ROUNDING_MODE) { rounded = getDefaultRoundedValue(); } else { rounded = internalRound(value, scale, roundingMode); } return newValueCopy(rounded, rounded); } /** * this value should be used for comparisons */ protected final V getDefaultRoundedValue() { if (defaultRoundedValue == null) { defaultRoundedValue = internalRound(value, Decimal.DEFAULT_ROUNDING_SCALE, Decimal.DEFAULT_ROUNDING_MODE); } return defaultRoundedValue; } protected final E newValueCopy(final V value) { return newValueCopy(value, null); } protected abstract E newValueCopy(V value, V defaultRoundedValue); protected abstract E getGenericThis(); public abstract E sqrt(); public abstract BigDecimal bigDecimalValue(); public abstract BigInteger bigIntegerValue(); public abstract int intValue(); public abstract long longValue(); public abstract float floatValue(); public abstract double doubleValue(); /** * This gives the raw value, thus not getting rounded for precision. */ public abstract double doubleValueRaw(); public abstract byte byteValue(); public abstract short shortValue(); public abstract Dfp dfpValue(); public abstract Number numberValue(); public abstract E log(); public abstract E exp(); public abstract E log10(); public abstract E exp10(); public abstract E cos(); public abstract E sin(); }