A realistic example where using BigDecimal for currency is strictly better than using double

I can see four basic ways that double can screw you when dealing with currency calculations.

Mantissa Too Small

With ~15 decimal digits of precision in the mantissa, you are you going to get the wrong result any time you deal with amounts larger than that. If you are tracking cents, problems would start to occur before 1013 (ten trillion) dollars.

While that's a big number, it's not that big. The US GDP of ~18 trillion exceeds it, so anything dealing with country or even corporation sized amounts could easily get the wrong answer.

Furthermore, there are plenty of ways that much smaller amounts could exceed this threshold during calculation. You might be doing a growth projection or a over a number of years, which results in a large final value. You might be doing a "what if" scenario analysis where various possible parameters are examined and some combination of parameters might result in very large values. You might be working under financial rules which allow fractions of a cent which could chop another two orders of magnitude or more off of your range, putting you roughly in line with the wealth of mere individuals in USD.

Finally, let's not take a US centric view of things. What about other currencies? One USD is worth is worth roughly 13,000 Indonesian Rupiah, so that's another 2 orders of magnitude you need to track currency amounts in that currency (assuming there are no "cents"!). You're almost getting down to amounts that are of interest to mere mortals.

Here is an example where a growth projection calculation starting from 1e9 at 5% goes wrong:

method   year                         amount           delta
double      0             $ 1,000,000,000.00
Decimal     0             $ 1,000,000,000.00  (0.0000000000)
double     10             $ 1,628,894,626.78
Decimal    10             $ 1,628,894,626.78  (0.0000004768)
double     20             $ 2,653,297,705.14
Decimal    20             $ 2,653,297,705.14  (0.0000023842)
double     30             $ 4,321,942,375.15
Decimal    30             $ 4,321,942,375.15  (0.0000057220)
double     40             $ 7,039,988,712.12
Decimal    40             $ 7,039,988,712.12  (0.0000123978)
double     50            $ 11,467,399,785.75
Decimal    50            $ 11,467,399,785.75  (0.0000247955)
double     60            $ 18,679,185,894.12
Decimal    60            $ 18,679,185,894.12  (0.0000534058)
double     70            $ 30,426,425,535.51
Decimal    70            $ 30,426,425,535.51  (0.0000915527)
double     80            $ 49,561,441,066.84
Decimal    80            $ 49,561,441,066.84  (0.0001678467)
double     90            $ 80,730,365,049.13
Decimal    90            $ 80,730,365,049.13  (0.0003051758)
double    100           $ 131,501,257,846.30
Decimal   100           $ 131,501,257,846.30  (0.0005645752)
double    110           $ 214,201,692,320.32
Decimal   110           $ 214,201,692,320.32  (0.0010375977)
double    120           $ 348,911,985,667.20
Decimal   120           $ 348,911,985,667.20  (0.0017700195)
double    130           $ 568,340,858,671.56
Decimal   130           $ 568,340,858,671.55  (0.0030517578)
double    140           $ 925,767,370,868.17
Decimal   140           $ 925,767,370,868.17  (0.0053710938)
double    150         $ 1,507,977,496,053.05
Decimal   150         $ 1,507,977,496,053.04  (0.0097656250)
double    160         $ 2,456,336,440,622.11
Decimal   160         $ 2,456,336,440,622.10  (0.0166015625)
double    170         $ 4,001,113,229,686.99
Decimal   170         $ 4,001,113,229,686.96  (0.0288085938)
double    180         $ 6,517,391,840,965.27
Decimal   180         $ 6,517,391,840,965.22  (0.0498046875)
double    190        $ 10,616,144,550,351.47
Decimal   190        $ 10,616,144,550,351.38  (0.0859375000)

The delta (difference between double and BigDecimal first hits > 1 cent at year 160, around 2 trillion (which might not be all that much 160 years from now), and of course just keeps getting worse.

Of course, the 53 bits of Mantissa mean that the relative error for this kind of calculation is likely to be very small (hopefully you don't lose your job over 1 cent out of 2 trillion). Indeed, the relative error basically holds fairly steady through most of the example. You could certainly organize it though so that you (for example) subtract two various with loss of precision in the mantissa resulting in an arbitrarily large error (exercise up to reader).

Changing Semantics

So you think you are pretty clever, and managed to come up with a rounding scheme that lets you use double and have exhaustively tested your methods on your local JVM. Go ahead and deploy it. Tomorrow or next week or whenever is worst for you, the results change and your tricks break.

Unlike almost every other basic language expression and certainly unlike integer or BigDecimal arithmetic, by default the results of many floating point expressions don't have a single standards defined value due to the strictfp feature. Platforms are free to use, at their discretion, higher precision intermediates, which may result in different results on different hardware, JVM versions, etc. The result, for the same inputs, may even vary at runtime when the method switches from interpreted to JIT-compiled!

If you had written your code in the pre-Java 1.2 days, you'd be pretty pissed when Java 1.2 suddenly introduces the now-default variable FP behavior. You might be tempted to just use strictfp everywhere and hope you don't run into any of the multitude of related bugs - but on some platforms you'd be throwing away much of the performance that double bought you in the first place.

There's nothing to say that the JVM spec won't again change in the future to accommodate further changes in FP hardware, or that the JVM implementors won't use the rope that the default non-strictfp behavior gives them to do something tricky.

Inexact Representations

As Roland pointed out in his answer, a key problem with double is that it doesn't have exact representations for some most non-integer values. Although a single non-exact value like 0.1 will often "roundtrip" OK in some scenarios (e.g., Double.toString(0.1).equals("0.1")), as soon as you do math on these imprecise values the error can compound, and this can be irrecoverable.

In particular, if you are "close" to a rounding point, e.g., ~1.005, you might get a value of 1.00499999... when the true value is 1.0050000001..., or vice-versa. Because the errors go in both directions, there is no rounding magic that can fix this. There is no way to tell if a value of 1.004999999... should be bumped up or not. Your roundToTwoPlaces() method (a type of double rounding) only works because it handled a case where 1.0049999 should be bumped up, but it will never be able to cross the boundary, e.g., if cumulative errors cause 1.0050000000001 to be turned into 1.00499999999999 it can't fix it.

You don't need big or small numbers to hit this. You only need some math and for the result to fall close to the boundary. The more math you do, the larger the possible deviations from the true result, and the more chance of straddling a boundary.

As requested here a searching test that does a simple calculation: amount * tax and rounds it to 2 decimal places (i.e., dollars and cents). There are a few rounding methods in there, the one currently used, roundToTwoPlacesB is a souped-up version of yours1 (by increasing the multiplier for n in the first rounding you make it a lot more sensitive - the original version fails right away on trivial inputs).

The test spits out the failures it finds, and they come in bunches. For example, the first few failures:

Failed for 1234.57 * 0.5000 = 617.28 vs 617.29
Raw result : 617.2850000000000000000000, Double.toString(): 617.29
Failed for 1234.61 * 0.5000 = 617.30 vs 617.31
Raw result : 617.3050000000000000000000, Double.toString(): 617.31
Failed for 1234.65 * 0.5000 = 617.32 vs 617.33
Raw result : 617.3250000000000000000000, Double.toString(): 617.33
Failed for 1234.69 * 0.5000 = 617.34 vs 617.35
Raw result : 617.3450000000000000000000, Double.toString(): 617.35

Note that the "raw result" (i.e., the exact unrounded result) is always close to a x.xx5000 boundary. Your rounding method errs both on the high and low sides. You can't fix it generically.

Imprecise Calculations

Several of the java.lang.Math methods don't require correctly rounded results, but rather allow errors of up to 2.5 ulp. Granted, you probably aren't going to be using the hyperbolic functions much with currency, but functions such as exp() and pow() often find their way into currency calculations and these only have an accuracy of 1 ulp. So the number is already "wrong" when it is returned.

This interacts with the "Inexact Representation" issue, since this type of error is much more serious than that from the normal mathematic operations which are at least choosing the best possible value from with the representable domain of double. It means that you can have many more round-boundary crossing events when you use these methods.


When you round double price = 0.615 to two decimal places, you get 0.61 (rounded down) but probably expected 0.62 (rounded up, because of the 5).

This is because double 0.615 is actually 0.6149999999999999911182158029987476766109466552734375.


The main problems you are facing in practice are related to the fact that round(a) + round(b) is not necessarily equal to round(a+b). By using BigDecimal you have fine control over the rounding process and can therefore make your sums come out correctly.

When you calculate taxes, say 18 % VAT, it is easy to get values that have more than two decimal places when represented exactly. So rounding becomes an issue.

Lets assume you buy 2 articles for $ 1.3 each

Article  Price  Price+VAT (exact)  Price+VAT (rounded)
A        1.3    1.534              1.53
B        1.3    1.534              1.53
sum      2.6    3.068              3.06
exact rounded   3.07

So if you do the calculations with double and only round to print the result, you would get a total of 3.07 while the amount on the bill should actually be 3.06.