package org.cryptocoinpartners.schema; import java.math.BigDecimal; import java.math.RoundingMode; import javax.persistence.Transient; import org.cryptocoinpartners.util.RemainderHandler; /** * @author Tim Olson */ @SuppressWarnings("UnusedDeclaration") public class DiscreteAmount extends Amount { /** helper for constructing an Amount with a double basis instead of long inverted basis. a double basis like * 0.05 is easier for human configurers than the inverted basis of 20 */ public static long invertBasis(double basis) { return Math.round(1 / basis); } /** for convenience of representation in properties files. we round to the nearest whole iBasis */ public static DiscreteAmountBuilder withBasis(double basis) { assert basis > 0; return new DiscreteAmountBuilder(invertBasis(basis)); } public static DiscreteAmountBuilder withIBasis(long iBasis) { assert iBasis > 0; return new DiscreteAmountBuilder(iBasis); } public static long roundedCountForBasis(BigDecimal amount, double basis) { return amount.divide(new BigDecimal(basis), mc).setScale(0, RoundingMode.HALF_UP).longValue(); } public static class DiscreteAmountBuilder { public DiscreteAmount fromCount(long count) { return new DiscreteAmount(count, iBasis); } public DiscreteAmount fromValue(BigDecimal input, RemainderHandler remainderHandler) { return new DecimalAmount(input).toIBasis(iBasis, remainderHandler); } private DiscreteAmountBuilder(long iBasis) { this.iBasis = iBasis; } private final long iBasis; } public DiscreteAmount(long count, long invertedBasis) { this.count = count; this.iBasis = invertedBasis; } public DiscreteAmount(long count, double basis) { this(count, invertBasis(basis)); } @Transient public long getCount() { return count; } protected void setCount(long count) { this.count = count; } /** adds one basis to the value by incrementing the count */ public DiscreteAmount increment() { return new DiscreteAmount(count + 1, iBasis); } /** adds to the value by incrementing the count by pips */ public DiscreteAmount increment(long pips) { return new DiscreteAmount(count + pips, iBasis); } /** subtracts one basis from the value by decrementing the count */ public DiscreteAmount decrement() { return new DiscreteAmount(count - 1, iBasis); } /** adds to the value by decrementing the count by pips */ public DiscreteAmount decrement(long pips) { return new DiscreteAmount(count - pips, iBasis); } @Override public DiscreteAmount negate() { return new DiscreteAmount(-count, iBasis); } public DiscreteAmount invertAsDiscreteAmount() { double value = ((double) count / (double) (iBasis * iBasis)); return value == 0 ? new DiscreteAmount(0, iBasis) : new DiscreteAmount((Math.round(1 / value)), iBasis); } @Override public Amount invert() { return new DecimalAmount(BigDecimal.ONE.divide(asBigDecimal(), mc)); } @Override public Amount plus(Amount o) { if (o instanceof DiscreteAmount) { DiscreteAmount discreteOther = (DiscreteAmount) o; if (iBasis == discreteOther.iBasis) return new DiscreteAmount(count + discreteOther.count, iBasis); } return new DecimalAmount(asBigDecimal().add(o.asBigDecimal())); } @Override public Amount minus(Amount o) { if (o instanceof DiscreteAmount) { DiscreteAmount discreteOther = (DiscreteAmount) o; if (iBasis == discreteOther.iBasis) return new DiscreteAmount(count - discreteOther.count, iBasis); } return new DecimalAmount(asBigDecimal().subtract(o.asBigDecimal())); } @Override public Amount times(Amount o, RemainderHandler remainderHandler) { // todo shouldn't this always maintain the basis? if (o instanceof DiscreteAmount) { DiscreteAmount discreteOther = (DiscreteAmount) o; if (iBasis == discreteOther.iBasis) return new DiscreteAmount(count * discreteOther.count, iBasis * discreteOther.iBasis); } return new DecimalAmount(asBigDecimal().multiply(o.asBigDecimal())); } @Override public Amount dividedBy(Amount o, RemainderHandler remainderHandler) { BigDecimal bdDivisor = o.asBigDecimal(); bdDivisor.setScale(Math.max(bdDivisor.scale(), mc.getPrecision())); return new DecimalAmount(asBigDecimal().divide(bdDivisor, BigDecimal.ROUND_HALF_EVEN)); } @Override public double asDouble() { return ((double) count) / iBasis; } @Override public BigDecimal asBigDecimal() { if (bd == null) bd = new BigDecimal(count).divide(new BigDecimal(iBasis), mc); return bd; } public long asLong() { return (count) / iBasis; } @Override public DiscreteAmount toIBasis(long newIBasis, RemainderHandler remainderHandler) { if (newIBasis % iBasis == 0) { // new basis is a multiple of old basis and has higher resolution. no remainder return new DiscreteAmount(count * (newIBasis / iBasis), newIBasis); } BigDecimal oldAmount = asBigDecimal(); long newCount = oldAmount.multiply(new BigDecimal(newIBasis), remainderHandler.getMathContext()).longValue(); DiscreteAmount newAmount = new DiscreteAmount(newCount, newIBasis); BigDecimal remainder = oldAmount.subtract(newAmount.asBigDecimal(), remainderHandler.getMathContext()); remainderHandler.handleRemainder(newAmount, remainder); return newAmount; } @Override @Transient public int getScale() { int length = (int) (Math.log10(iBasis)); return length; } @Transient public double getBasis() { BigDecimal bd = new BigDecimal(iBasis); return (bd.ONE.divide(bd)).doubleValue(); } @Override public int compareTo(@SuppressWarnings("NullableProblems") Amount o) { if (o instanceof DiscreteAmount) { DiscreteAmount discreteAmount = (DiscreteAmount) o; if (discreteAmount.iBasis == iBasis) return Long.compare(count, discreteAmount.count); } return asBigDecimal().compareTo(o.asBigDecimal()); } @Override public void assertIBasis(long otherIBasis) { if (iBasis != otherIBasis) throw new BasisError(); } @Override @Transient public boolean isPositive() { return count > 0; } @Override @Transient public boolean isZero() { return count == 0; } @Override @Transient public boolean isNegative() { return count < 0; } @Override public boolean equals(Object object) { boolean result = false; if (object == null || object.getClass() != getClass()) { result = false; } else { DiscreteAmount amount = (DiscreteAmount) object; if (this.getCount() == amount.getCount() && this.getBasis() == amount.getBasis()) { result = true; } } return result; } @Override public int hashCode() { int hash = 3; hash = 7 * hash + Long.valueOf(this.getCount()).hashCode(); hash = 7 * hash + Double.valueOf(this.getBasis()).hashCode(); return hash; } // JPA protected DiscreteAmount() { } /** * The invertedBasis is 1/basis. This is done because we can then use a long integer instead of double, knowing * that all bases must be integral factors of 1.<br/> * Example inverted bases: quarters=4, dimes=10, nickels=20, pennies=100, satoshis=1e8 */ private long iBasis = 0; protected long count; private BigDecimal bd; }