# Java's Floating-Point (Im)Precision

I was working with someone recently who told me he was having intermittent issues dealing with double floating-point values in Java. When he added and subtracted dollar amounts in this financial application, using the Java primitive type `double`

, he would sometimes get unexpected results. For example, the following code shows a sample of some calculations he found troubling (other combinations of values worked fine):

double a = 106838.81; double b = 263970.96; double c = 879.35; double d = 366790.80; double total = 0; total += a; total += b; total -= c; total -= d;

At the end of this operation, he expected the value `3139.62`

, but instead was getting `3139.6200000000536`

. When formatted for display as a dollar amount, it was appearing as expected: "$3139.62." However, when he later inspected the database where he stored results, he noticed the issue. The trouble only came to light when he started seeing "$-0.00" after formatting for currency display. A quick look at the database showed the actual value as `-0.000000000053518078857450746`

. What was going on to cause this, and how could it be fixed?

The problem derives from how `float`

and `double`

floating-point values are stored internally by the JVM. Unlike `int`

and `long`

(and other fixed-point types) that are stored as exact binary representations of the numbers they're assigned to, shortcuts are taken with `float`

and `double`

. Internally, Java stores values for these types with an inexact representation, using only a portion of the 64 bits for the significant digits. As a result, Java doesn't store, calculate, or return the exact representation of the actual floating-point value in some cases. This seemingly intermittent behavior can be annoying, as it only becomes apparent with specific combinations of numbers and operations.

### BigDecimal to the Rescue

Fortunately, Java provides a math package, java.math.*, which includes the `BigDecimal`

class. `BigDecimal`

can be used to alleviate the rounding and loss of precision issues that are often seen with double floating-point arithmetic. `BigDecimal`

allows you to specify precisely how the rounding behavior should work using the `java.math.MathContext`

class. The number of digits to be returned can be specified with this object as well. Let's look at some examples:

// The following code returns 1.5500000000000000444089209850062616169452667236328125 BigDecimal bd = new BigDecimal(1.55); // The following code returns 1.550000 BigDecimal bd = new BigDecimal(1.55, MathContext.DECIMAL32); // The following code returns 1.550000000000000 BigDecimal bd = new BigDecimal(1.55, MathContext.DECIMAL64);

Above, you see how the constructor allows you to specify the precision used to store and work with the floating-point value. Let's look at how to specify rounding, which must occur when the exact value cannot be represented with the precision used. First, note that the scale of the `BigDecimal`

floating-point value indicates the number of digits to the right of the right of the decimal point.

Each of the following returns 1.55:

BigDecimal bd = new BigDecimal(1.55, MathContext.DECIMAL32); bd = bd.setScale(2); BigDecimal bd = new BigDecimal(1.55, MathContext.DECIMAL64); bd = bd.setScale(2);

However, due to the default precision used, the following throws an exception indicating that rounding is necessary:

BigDecimal bd = new BigDecimal(1.55); bd = bd.setScale(2);

There are multiple rounding types (to round up, down, using ceiling or floor operators, and so on), and you can specify the kind you want as the second parameter when setting the scale:

BigDecimal bd = new BigDecimal(1.55); bd = bd.setScale(2, BigDecimal.ROUND_DOWN);

Remember that the `BigDecimal`

class is immutable, and simply calling a method on a `BigDecimal`

object will have no affect, so you need to reassign it after every call. Let's revisit the first example showing the set of floating-point operations that yielded unexpected results, but modified to use `BigDecimal`

properly:

double a = 106838.81; double b = 263970.96; double c = 879.35; double d = 366790.80; BigDecimal total = new BigDecimal(0, MathContext.DECIMAL64); total = total.setScale(2); total = total.add(new BigDecimal(a, MathContext.DECIMAL64)); total = total.add(new BigDecimal(b, MathContext.DECIMAL64)); total = total.subtract(new BigDecimal(c, MathContext.DECIMAL64)); total = total.subtract(new BigDecimal(d, MathContext.DECIMAL64));

In this case, the precision is set to 64 bits, and the scale is set to 2 to adequately represent currency values. Additionally, the results of calls to add and subtract are reassigned to the original `BigDecimal`

object because it's immutable. I avoid the verbose code (and the typing required) to set the precision and scale, and instead use helper methods, such as:

private BigDecimal doubleToBD32(double val) { return new BigDecimal(val, MathContext.DECIMAL64).setScale(2); } private BigDecimal doubleToBD64(double val) { return new BigDecimal(val, MathContext.DECIMAL64).setScale(2); } private BigDecimal doubleToBD128(double val) { return new BigDecimal(val, MathContext.DECIMAL128).setScale(2); } private BigDecimal doubleToBD(double val) { return new BigDecimal(val, MathContext.UNLIMITED).setScale(2); }

As a result, the final code looks like this, which is more pleasing to me:

double a = 106838.81; double b = 263970.96; double c = 879.35; double d = 366790.80; BigDecimal total = doubleToBD64(0); total = total.add( doubleToBD64(a) ); total = total.add( doubleToBD64(b) ); total = total.subtract( doubleToBD64(c) ); total = total.subtract( doubleToBD64(d) );

Although there are many, many more details involved and other options when using `BigDecimal`

and `MathContext`

(and the java.math package for that matter), I hope this quick overview helps if you ever get bit by Java's binary representation of floating-point and double floating-point numbers and arithmetic operations.

Happy coding!

—EJB

## Comments:

2014-08-22T19:04:26

Thank you for that thorough explanation. I'll check out that paper. Thanks again.

Permalink

2014-08-21T23:55:33

Floating point does give exact answers given the right conditions.

E.g. if you supply a value with five decimal digits before conversion to decimal, you should convert to a maximum of five digits when converting back to decimal.

Double gives you 16 decimal digits of precision. So if you convert a decimal number with 16 digit or less to FP and back you get the exact result. But you need to round to the number of significant digits you expect since FP does not keep track of that for you.

Then you may lose precision during operations, For multiplication and division, a rule of thumb is that you lose at most one digit per operation. For addition and subtraction it is more complicated, but think of it as aligning around the decimal point. If the maximum number of digit before and after the decimal point, including the result, overall is larger than 16 digit, you lose precision, otherwise you don't.

In your example you need eight digits, well withing the precision of double, so the result when converted back to decimal with the significant number of digits, is in fact exact. The error in the original code is that it outputs the results with excess precision.

IEEE floating point is exact, as long as you don't overstep the precision, which means you need to estimate the total error in your calculations regardless of whether you are counting pennies or forecasting the weather.

For money this is problematic because most calculations fit fine within the precision of double, but just barely and many accountants expect that even amounts of a thousand trillions are computed to the penny, because that's how debit and credit are balanced.

As for "can't use for money", I think that statement is invalid. Excel is the most widely used tool for monetary calculations and it uses floating point. Sure people make huge mistakes in their spreadsheets, but that's not due to the use of floating point. Trading systems also use floating point heavily, so apparently it works quite well.

C/C++ programmers on many platforms can use long double which has 35 or so decimal digits which allows for quite a lot of calculations before any significant loss of precision occurs.

Of course the safe way is to use BigDecimal and only BigDecimal. You mix double and BigDecimal which bad and is likely to get you into trouble someday. I've seen worse though. BigDecimal has a constructor from String, so you could write this instead that has no floating point at all.

BigDecimal a = new BigDecimal("106838.81");

BigDecimal b = new BigDecimal("263970.96");

BigDecimal c = new BigDecimal("879.35");

BigDecimal d = new BigDecimal("366790.80");

BigDecimal total = BigDecimal.ZERO;

total = total.add( a );

total = total.add( b);

total = total.subtract( c );

total = total.subtract( d );

There is a paper called "What Every Computer Scientist Should Know About Floating Point" that explains all the details of when you can expect exact results from floating point.

Permalink

2014-08-04T14:57:39

I think it's not just that people don't know about BigDecimal (they probably pass it in JavaDoc many times), but that using BigDecimal means (1) your formulas become far more obtuse and hard to read, and (2) you can't use floating-point libraries and frameworks.

Leaving operator overloading out of Java was probably a good thing, but it makes it a bad language for finance. I'd say just use Scala, but then you'd have to spend two years learning Scala-- which is made harder because of... operator overloading.

Permalink

2014-07-30T17:49:59

Ha! You caught us tripping over our attempts to be clever in headline writing. In all fairness to Eric, that was my goof in editing the blog post. The correction has been made.

Permalink

2014-07-29T21:00:57

"Inprecise" is more imprecise than floating-point! :)

Permalink

2014-07-29T20:53:41

I am more mystified by the title. I was looking for the 'hook' as to why floating point math is 'in-precise' as opposed to 'imprecise'. I guess it was just a typo. Please be more careful when using terms that are either incorrect or imprecise, as they are more misleading in the context in which they are used.

Permalink

2014-07-24T22:54:51

It is ridiculous, as you state. This problem has been known for years and, esp. in Java, there are classes specifically created to solve this problem (that is, the BigDecimal that Eric discusses). This is where, I believe, the self-taught history of some programmers comes back to bite them: they don't know what they don't know.

Permalink

2014-07-24T22:09:08

I'm utterly depressed by the fact that twenty years after even I started working in banking I.T there still seem to be people who don't know that you can't use fp data types for financial calculations.

Please RTFM!

No matter what the language I'm sure it will say the same thing.

Permalink

2014-07-24T18:10:32

Thanks for the comment. I focused on Java since this is a Java specific blog, that's why I didn't say it was a flaw or bug. I didn't compare it to the behavior of other compilers and runtimes. Instead I focused on how to properly handle floating point math for financial calculations in Java using BigDecimal. I apologize if the title is misleading.

Permalink

2014-07-24T15:03:32

I'm mystified by this article. Not the observed behavior, but the explanation.

Here's the output from the same initial code using C++ (g++ on Linux Mint):

total: 3139.6200000000535510

That's the same result because it's calculated using IEEE 754 double precision floating point math - just as it is in Java. IEEE floating point math is well known to be unsuitable for financial calculations.

Please make it clear that this is in no way unique to Java, as even the title implies. It's simply a function of the normal behavior of IEEE floating point.

Permalink