This article discusses how to perform monetary operations in Java: what is the correct way of using double
and what alternatives do we have.
Monetary operations using long/double
The easiest way to represent monetary values in financial environment is to work with the smallest currency units – for example, cents in USA, instead of normal currency unit – dollar in USA. long
datatype is rather suitable for this case. Unfortunately, sometimes we have to divide such values or multiply them by decimal point values (for example, calculate how much you have earned on your savings account). This means that while we can still use long
for storing cents, we need to multiply/divide using decimal point arithmetic.
Do not use float
for any monetary operations unless you absolutely sure. It has too low precision (23 bits).
double
calculations are not precise. Even simple one, such as addition and subtraction:
1 System.out.println( "362.2  362.6 = " + ( 362.2  362.6 ) );System.out.println( "362.2  362.6 = " + ( 362.2  362.6 ) );
will print "362.2  362.6 = 0.4000000000000341"
. This means we should:
 Avoid working with nonintegral values while using
double
(calculate in the smallest currency units).  Round any multiplication/division results using
Math.round/rint/ceil/floor
(per your system requirements).
It is still possible to add/subtract using long
as long as you stick to 2 rules described above.
Let’s see how long it will take us to calculate 1.5% from 362.2$ using double
and BigDecimal
. The following loops were run 100M times:
1 2 3 4 5 6 7 8 int res = 0; final BigDecimal orig = new BigDecimal( "362.2" ); final BigDecimal mult = new BigDecimal( "0.015" ); //1.5% for ( int i = 0; i < ITERS; ++i ) { final BigDecimal result = orig.multiply( mult, MathContext.DECIMAL64 ); if ( result != null ) res++; }int res = 0; final BigDecimal orig = new BigDecimal( "362.2" ); final BigDecimal mult = new BigDecimal( "0.015" ); //1.5% for ( int i = 0; i < ITERS; ++i ) { final BigDecimal result = orig.multiply( mult, MathContext.DECIMAL64 ); if ( result != null ) res++; }
We can not exactly emulate the previous calculation using double
and long
. In the following code JIT will move a constant Math.round( orig * mult )
out of the loop:
1 2 3 4 5 6 7 final double orig = 36220; //362.2 in cents final double mult = 0.015; //1.5% for ( int i = 0; i < ITERS; ++i ) { final long result = Math.round( orig * mult ); if ( result != 543 ) res++; //543.3 cents actually }final double orig = 36220; //362.2 in cents final double mult = 0.015; //1.5% for ( int i = 0; i < ITERS; ++i ) { final long result = Math.round( orig * mult ); if ( result != 543 ) res++; //543.3 cents actually }
So, we will use slightly different test in this case:
1 2 3 4 5 6 final double orig = 36220; //362.2 in cents for ( long i = 0; i < ITERS; ++i ) { final long result = Math.round( orig * i ); if ( result != 543 ) res++; //compare with something }final double orig = 36220; //362.2 in cents for ( long i = 0; i < ITERS; ++i ) { final long result = Math.round( orig * i ); if ( result != 543 ) res++; //compare with something }
It took 4.899 sec for BigDecimal
calculations and 0.58 sec for double calculations.
As you can see, as long as your calculations fit in 53 bits (double
precision) and you stick to the 2 rules listed above, you will have a fast and precise monetary calculations in Java.
Monetary calculations using BigDecimal
If you still want to use BigDecimal
for your monetary calculations (it is convenient at least!), the following text will give you some hints on the correct BigDecimal
usage.
For BigDecimal
s you can specify both rounding mode and precision, if you need it, but there is a more convenient way  you can use MathContext
instead, which contains both precision and rounding information. More important, there are a few predefined ones, which will allow you to emulate float/double/decimal_128
arithmetic operations without any rounding problems: MathContext.DECIMAL32/DECIMAL64/DECIMAL128
. There is also MathContext.UNLIMITED
, which is a default MathContext
value.
You can use addition and subtraction without MathContext
, but for multiplication and division you'd better specify one of DECIMAL*
contexts. They are required because these operations require a precision to be specified in case when an operation result has an infinitely long decimal expansion; for example, 1 divided by 3. Otherwise you'll get ArithmeticException: Nonterminating decimal expansion; no exact representable decimal result
. Try it:
1 2 3 4 5 6 7 8 9 final BigDecimal three = new BigDecimal( "3" ); try { System.out.println( BigDecimal.ONE.divide( three ) ); } catch ( ArithmeticException ex ) { System.out.println( "Got an exception while calculating 1/3 : " + ex.getMessage() ); }final BigDecimal three = new BigDecimal( "3" ); try { System.out.println( BigDecimal.ONE.divide( three ) ); } catch ( ArithmeticException ex ) { System.out.println( "Got an exception while calculating 1/3 : " + ex.getMessage() ); }
How fast are BigDecimal
arithmetic operations? Let's calculate a sum of 10M E*E+E
, where E = Math.E
:
1 2 3 4 5 6 7 8 9 BigDecimal res = BigDecimal.ZERO; final BigDecimal a = new BigDecimal( Math.E, context ); final BigDecimal b = new BigDecimal( Math.E, context ); final BigDecimal c = new BigDecimal( Math.E, context ); for ( int i = 0; i < 10000000; ++i ) { final BigDecimal val = a.multiply( b, context ).add( c, context ); res = res.add( val, context ); }BigDecimal res = BigDecimal.ZERO; final BigDecimal a = new BigDecimal( Math.E, context ); final BigDecimal b = new BigDecimal( Math.E, context ); final BigDecimal c = new BigDecimal( Math.E, context ); for ( int i = 0; i < 10000000; ++i ) { final BigDecimal val = a.multiply( b, context ).add( c, context ); res = res.add( val, context ); }
In addition to this method, we will run another one for BigDecimal
, where MathContext
is not used. And the last one, just to compare, will calculate the same expression using double
. The last column in the table is result of calculation in a given mode.
Type  Time  Result 
double  0.018 sec  1.010733792587689E8 
no MathContext  4.1 sec  101073379.273896945320908905278183855697464192452494578591950602844407036684515333035960793495178222656250000000 
MathContext.UNLIMITED  3.9 sec  101073379.273896945320908905278183855697464192452494578591950602844407036684515333035960793495178222656250000000 
MathContext.DECIMAL32  4.2 sec  100000000 
MathContext.DECIMAL64  9.5 sec  101073379.2938854 
MathContext.DECIMAL128  13.9 sec  101073379.2738969453209089052948157 
There are a lot of cases when you can avoid using BigDecimal
arithmetic operations. For example, if you have a number represented as a String
and you want to multiply it by 10^N
(and N
is your input), when it will be faster to write some code which moves decimal point left or right for a given input.
Other example  if you have a double
and want to multiply or divide it by power of 2  in general case you should have correct results for such calculations, because exponent part of floating point numbers represent a power of 2.
Converting a number to a string
How easy is it to convert a floating point number to a String
(and get a correct result)? As it turns out  rather difficult. For double
conversion, for example, you need to know a binary representation of a floating point number (IEEE754). For details take a look at the source of sun.misc.FloatingDecimal
class (usually not shipped with your JDK, so google it ðŸ™‚ ), in particular, to a very short dtoa
method, which does all the magic. One may conclude from that method comments that nobody dared to touch it since 1996 ðŸ™‚
Java 6 has an interesting sequence of calls required to convert a Double
(object!) to String
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Double public String toString() { return String.valueOf(value); } String public static String valueOf(double d) { return Double.toString(d); } Double public static String toString(double d) { return new FloatingDecimal(d).toJavaFormatString(); }Double public String toString() { return String.valueOf(value); } String public static String valueOf(double d) { return Double.toString(d); } Double public static String toString(double d) { return new FloatingDecimal(d).toJavaFormatString(); }
In Java 7 they finally went the shortest road:
1 2 3 4 jdk 7 Double public String toString() { return toString(value); }jdk 7 Double public String toString() { return toString(value); }
BigDecimal
has 3 methods for converting it to a String
: toString
, toPlainString
and toEngineeringString
. toString
is caching result of toEngineeringString
inside a BigDecimal
and using it on subsequent calls (BigDecimal
value is immutable, so this trick is possible for it). Let's see how fast Math.E
(for BigDecimals
it is converted to BigDecimal
in advance) is converted to String
10M times:
Double.toString(double) 
BigDecimal.toPlainString 
BigDecimal.toEngineeringString 
4.1 sec  12.4 sec  12.5 sec 
By the way, it is a bad idea to convert double
to BigDecimal
: double
is converted to String
first and then a String
is converted to a BigDecimal
. So, if your algorithm gets a String
as an input, convert it straight to BigDecimal
. As a bonus, you will avoid any rounding errors.
See also
If you want to see how to efficiently implement a Money class, take a look at Implementing a high performance Money class article.
Summary

If you want to implement fast and correct monetary arithmetic operations in Java, stick to the following rules:
 Store monetary values in the smallest currency units (for example, cents) in
long
variables.  Avoid working with nonintegral values while using
double
(calculate in the smallest currency units).  Add/subtract using
long
.  Round any multiplication/division results using
Math.round/rint/ceil/floor
(per your system requirements).  Your calculations should fit into 52 bits (
double
precision).
 Store monetary values in the smallest currency units (for example, cents) in

Always use
MathContext
forBigDecimal
multiplication and division in order to avoidArithmeticException
for infinitely long decimal results. Don't useMathContext.UNLIMITED
for that reason  it is equivalent to no context at all. 
Do not convert
double
toBigDecimal
, instead convertString
toBigDecimal
when possible.
Pingback: Implementing a high performance Money classÂ Â  Java Performance Tuning Guide