package org.cryptocoinpartners.schema; import java.io.Serializable; import java.math.BigDecimal; import java.math.MathContext; import javax.persistence.MappedSuperclass; import javax.persistence.Transient; import org.cryptocoinpartners.util.RemainderHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Amount has a polymorphic base representation of either a BigDecimal or a special long/long format * which captures discrete amounts as a count plus a basis. The basis is stored inverted so it can be represented * as a long instead of a double. Long/long is the preferred format for discrete amounts like trading volumes and * prices, while I/O prefers BigDecimal. * Whenever a downcast or rounding happens, the remainders are recorded, along with a RemainderHandler delegate which * is to dispatch of the remainder. No calls to the RemainderHandlers are invoked until settle() is called on the * Amount, at which time all accumulated remainders since the last settle() are pushed to their respective * RemainderHandlers. * * @author Tim Olson */ @MappedSuperclass public abstract class Amount implements Comparable<Amount>, Serializable { /** * */ private static final long serialVersionUID = 6644052682251199772L; public static final MathContext mc = MathContext.DECIMAL128; // IEEE 128-bit decimal, scale 34 /** * only when absolutely necessary */ public abstract double asDouble(); public abstract BigDecimal asBigDecimal(); @Transient public abstract int getScale(); public DiscreteAmount toBasis(double newBasis, RemainderHandler remainderHandler) { long newIBasis = DiscreteAmount.invertBasis(newBasis); return toIBasis(newIBasis, remainderHandler); } public abstract DiscreteAmount toIBasis(long newIBasis, RemainderHandler remainderHandler); public void assertBasis(double basis) throws BasisError { long otherIBasis = DiscreteAmount.invertBasis(basis); assertIBasis(otherIBasis); } public abstract void assertIBasis(long otherIBasis); @Override public String toString() { return asBigDecimal().toString(); } public class BasisError extends Error { } public Amount abs() { return isNegative() ? negate() : this; } @Transient public abstract boolean isPositive(); @Transient public abstract boolean isZero(); @Transient public abstract boolean isNegative(); @Transient public abstract Amount negate(); @Transient public abstract Amount plus(Amount o); @Transient public abstract Amount minus(Amount o); @Transient public DecimalAmount times(BigDecimal o, RemainderHandler remainderHandler) { return new DecimalAmount(asBigDecimal().multiply(o, remainderHandler.getMathContext())); } @Transient public DecimalAmount dividedBy(BigDecimal o, RemainderHandler remainderHandler) { BigDecimal[] divideAndRemainder = asBigDecimal().divideAndRemainder(o, remainderHandler.getMathContext()); DecimalAmount result = new DecimalAmount(divideAndRemainder[0]); remainderHandler.handleRemainder(result, divideAndRemainder[1]); return result; } @Transient public DecimalAmount divide(BigDecimal o, RemainderHandler remainderHandler) { o.setScale(Math.min(o.scale(), mc.getPrecision())); BigDecimal division = asBigDecimal().divide(o, remainderHandler.getRoundingMode()); DecimalAmount result = DecimalAmount.of(division); return result; } @Transient public Amount times(int o, RemainderHandler remainderHandler) { return times(new BigDecimal(o), remainderHandler); } @Transient public Amount dividedBy(int o, RemainderHandler remainderHandler) { return dividedBy(new BigDecimal(o), remainderHandler); } @Transient public DecimalAmount divide(int o, RemainderHandler remainderHandler) { BigDecimal division = asBigDecimal().divide(BigDecimal.valueOf(o), remainderHandler.getRoundingMode()); DecimalAmount result = DecimalAmount.of(division); return result; } @Transient public Amount times(double o, RemainderHandler remainderHandler) { return times(new BigDecimal(o), remainderHandler); } @Transient public Amount dividedBy(double o, RemainderHandler remainderHandler) { return dividedBy(new BigDecimal(o), remainderHandler); } @Transient public abstract Amount times(Amount o, RemainderHandler remainderHandler); @Transient public abstract Amount dividedBy(Amount o, RemainderHandler remainderHandler); protected static final Logger log = LoggerFactory.getLogger(Amount.class); @Transient public abstract Amount invert(); }