/* * Copyright 2014 Mikhail Vorontsov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package info.javaperformance.money; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; /** * The most efficient implementation storing the number of currency units in <code>long</code> field. */ class MoneyLong extends AbstractMoney { /** Number of currency units in your precision */ private final long m_units; /** Precision of your value. You should divide <code>m_units</code> by <code>10^m_precision</code> to get an actual value */ private final int m_precision; public MoneyLong(long units, int precision) { m_units = units; m_precision = precision; } /** * Convert to the original currency - divide <code>units</code> by <code>10^precision</code>. * @return <code>units / (10^precision)</code> */ public double toDouble() { //we can not replace division here with multiplication by MULTIPLIERS_NEG - it will sacrifice the exact result guarantee. return ( ( double ) m_units ) / MoneyFactory.MULTIPLIERS[ m_precision ]; } /** * Convert into a String in a plain notation with a decimal dot. * @return a String in a plain notation with a decimal dot. */ @Override public String toString() { if ( m_precision == 0 ) return Long.toString(m_units); final char[] buf = new char[ MoneyFactory.MAX_LONG_LENGTH + 3 ]; //do not replace with ThreadLocal - it is slower int p = buf.length; int remainingPrecision = m_precision; long units = Math.abs( m_units ); long q; int rem; while ( remainingPrecision > 0 && units > 0 ) { q = units / 10; rem = (int) (units - q * 10); //avoiding direct % call buf[ --p ] = (char) ('0' + rem); units = q; --remainingPrecision; } if ( units == 0 && remainingPrecision == 0 ) //just add "0." { buf[ --p ] = '.'; buf[ --p ] = '0'; } else if ( units == 0 ) //some precision left { while ( remainingPrecision > 0 ) { buf[ --p ] = '0'; --remainingPrecision; } buf[ --p ] = '.'; buf[ --p ] = '0'; } else if ( remainingPrecision == 0 ) //some value left { buf[ --p ] = '.'; while ( units > 0 ) { q = units / 10; rem = (int) (units - q * 10); buf[ --p ] = (char) ('0' + rem); units = q; } } if ( m_units < 0 ) buf[ --p ] = '-'; return new String( buf, p, buf.length - p ); } /** * Convert this value into a BigDecimal. This method is also used for arithmetic calculations when necessary. * * @return This object as BigDecimal */ public BigDecimal toBigDecimal() { return BigDecimal.valueOf( m_units, m_precision ); } /** * Return this value with an opposite sign. * * @return A new object with the same value with a different sign */ public Money negate() { return new MoneyLong( -m_units, m_precision ); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MoneyLong moneyLong = (MoneyLong) o; return m_precision == moneyLong.m_precision && m_units == moneyLong.m_units; } @Override public int hashCode() { final int result = (int) (m_units ^ (m_units >>> 32)); return 31 * result + m_precision; } public Money add( final MoneyLong other ) { long normUnitsRes; int precision = m_precision; if ( m_precision == other.m_precision ) normUnitsRes = m_units + other.m_units; else if ( m_precision > other.m_precision ) { long multiplier = MoneyFactory.MULTIPLIERS[m_precision - other.m_precision]; long mult = other.m_units * multiplier; if ( mult / multiplier != other.m_units ) //overflow check, alternative is double multiplication and compare with Long.MAX_VALUE. return other.add( new MoneyBigDecimal( toBigDecimal() ) ); normUnitsRes = m_units + mult; } else { long multiplier = MoneyFactory.MULTIPLIERS[other.m_precision - m_precision]; long mult = m_units * multiplier; if ( mult / multiplier != m_units ) //overflow check return other.add( new MoneyBigDecimal( toBigDecimal() ) ); normUnitsRes = mult + other.m_units; precision = other.m_precision; } //cheap overflow check, it does not cover a case when normUnitsRes get positive if ( m_units >= 0 && other.m_units >= 0 && normUnitsRes < 0 ) return other.add( new MoneyBigDecimal( toBigDecimal() ) ); return new MoneyLong( normUnitsRes, precision ).normalize(); } private static int compare( final long x, final long y ) { return ( x < y ) ? -1 : ( ( x == y ) ? 0 : 1 ); } @Override protected int compareTo( final MoneyLong other ) { if ( m_precision == other.m_precision ) return compare( m_units, other.m_units ); if ( m_precision < other.m_precision ) { final long multiplier = MoneyFactory.MULTIPLIERS[ other.m_precision - m_precision ]; final long mult = m_units * multiplier; if ( mult / multiplier == m_units ) //overflow check return compare( mult, other.m_units ); } if ( m_precision > other.m_precision ) { final long multiplier = MoneyFactory.MULTIPLIERS[ m_precision - other.m_precision ]; final long mult = other.m_units * multiplier; if ( mult / multiplier == other.m_units ) //overflow check return compare( m_units, mult ); } //fallback for generic case return toBigDecimal().compareTo(other.toBigDecimal()); } /** * If <code>m_units</code> ends with zeroes - reduce the <code>m_precision</code> accordingly * @return Normalized value */ MoneyLong normalize() { //shortcut - must be an even number (to be divisible by 10) if ( ( m_units & 1 ) == 1 ) return this; int precision = m_precision; long units = m_units; long q, rem; while ( precision > 0 ) { //todo move odd check here? q = units / 10; rem = units - ( (q << 3) + ( q << 1 ) ); if ( rem != 0 ) break; --precision; units = q; } if ( precision == m_precision ) return this; else return new MoneyLong( units, precision ); } private static final long MASK32 = 0xFFFFFFFF00000000L; /** * Multiply the current object by the <code>long</code> value. * * @param multiplier Multiplier * @return A new Money object normalized to the efficient representation if possible */ public Money multiply( long multiplier ) { final long resUnits = m_units * multiplier; //fast overflow test - if both values fit in the 32 bits (and positive), they can not overflow if ( ( (m_units | multiplier) & MASK32 ) == 0 ) return new MoneyLong( resUnits, m_precision ).normalize(); //slower overflow test - check if we will get the original value back after division. It is not possible //in case of overflow. final long origUnits = resUnits / multiplier; if ( origUnits != m_units ) { final BigInteger res = BigInteger.valueOf( m_units ).multiply( BigInteger.valueOf( multiplier ) ); return MoneyFactory.fromBigDecimal( new BigDecimal( res ) ); } return new MoneyLong( resUnits, m_precision ).normalize(); } /** * Multiply the current object by the <code>double</code> value. * * @param multiplier Multiplier * @return A new Money object normalized to the efficient representation if possible */ public Money multiply( final double multiplier ) { final double unscaledRes = m_units * multiplier; //need to apply precision //try to check if we got an integer value first final long unscaledLng = (long) unscaledRes; if ( unscaledLng == unscaledRes ) //possible overflow is also checked here return new MoneyLong( unscaledLng, m_precision ).normalize(); //4 is a "safe" precision of this calculation. The higher it is - the less results will end up //on the BD branch, but at the same time the more expensive the normalization will be. final MoneyLong unscaledLong = MoneyFactory.fromDoubleNoFallback( unscaledRes, 4 ); if ( unscaledLong != null ) { //if precision is not too high - stay at long values if ( unscaledLong.m_precision + m_precision <= MoneyFactory.MAX_ALLOWED_PRECISION ) return new MoneyLong( unscaledLong.m_units, unscaledLong.m_precision + m_precision ).normalize(); } //slow path via BD. We may still get MoneyLong on this branch if the unscaledRes precision is too high. return MoneyFactory.fromBigDecimal( toBigDecimal().multiply( new BigDecimal( multiplier, MathContext.DECIMAL64 ), MathContext.DECIMAL64 ) ); } /** * Divide the current object by the given <code>long</code> divider. * * @param divider Divider * @param precision Maximal precision to keep. We will round the next digit. * @return A new Money object normalized to the efficient representation if possible */ public Money divide( final long divider, final int precision ) { return divide( ( double ) divider, precision ); } /** * Divide the current object by the given <code>long</code> divider. * * @param divider Divider * @param precision Maximal precision to keep. We will round the next digit. * @return A new Money object normalized to the efficient representation if possible */ public Money divide( final double divider, final int precision ) { if ( precision > MoneyFactory.MAX_ALLOWED_PRECISION ) return new MoneyBigDecimal( toBigDecimal() ).divide( divider, precision ); final double unscaledRes = m_units / divider; //We already have m_precision digits of precision. We need to take (precision-m_precision) digits //more from the unscaled result. Plus one more digit for rounding. final long destRes; if ( m_precision < precision ) { //take precision-m_precision digits after decimal point destRes = Math.round( unscaledRes * MoneyFactory.MULTIPLIERS[ precision - m_precision ] ); } else if ( m_precision == precision ) { //round to long destRes = Math.round( unscaledRes ); } else // if ( m_precision > precision ) { //m_units = 135, m_precision=3, precision = 2 => round 13.5 to 14 //we can multiply by a floating point value here because we will round the result destRes = Math.round( unscaledRes * MoneyFactory.MULTIPLIERS_NEG[ m_precision - precision ] ); } return new MoneyLong( destRes, precision ).normalize(); } /** * Truncate the current value leaving no more than {@code maximalPrecision} signs after decimal point. * The number will be rounded towards closest digit (0-4 -> 0; 5-9 -> 1) * * @param maximalPrecision Required precision * @return A new Money object normalized to the efficient representation if possible */ public Money truncate( final int maximalPrecision ) { if ( m_precision <= maximalPrecision ) return this; MoneyFactory.checkPrecision( maximalPrecision ); //remove not needed digits //we can multiply by floating point values here because we will round the result afterwards return new MoneyLong( Math.round( m_units * MoneyFactory.MULTIPLIERS_NEG[ m_precision - maximalPrecision ] ), maximalPrecision ).normalize(); } }