Java BigDecimal precision problems
Asked Answered
C

5

15

I know the following behavior is an old problem, but still I don't understand.

System.out.println(0.1 + 0.1 + 0.1);    

Or even though I use BigDecimal

System.out.println(new BigDecimal(0.1).doubleValue()
    + new BigDecimal(0.1).doubleValue()
    + new BigDecimal(0.1).doubleValue());

Why this result is: 0.30000000000000004 instead of: 0.3?

How can I solve this?

Charcoal answered 20/3, 2012 at 21:36 Comment(3)
You ... aren't using BigDecimal. You're doing exactly the same thing as adding the doubles. doubleValue() returns ... a double. See the Javadoc for BigDecimal on how to add/subtract/etcMozellemozes
Re the double calculations, see What Every Computer Scientist Should Know About Floating-Point Arithmetic. Java solves this by providing format options for output.Survivor
Why are you using doubleValue()??? I thought you wanted a decimal type.Pedicle
U
32

What you actually want is

new BigDecimal("0.1")
 .add(new BigDecimal("0.1"))
 .add(new BigDecimal("0.1"));

The new BigDecimal(double) constructor gets all the imprecision of the double, so by the time you've said 0.1, you've already introduced the rounding error. Using the String constructor avoids the rounding error associated with going via the double.

Undergird answered 20/3, 2012 at 22:7 Comment(4)
thanks for all! Now... I can understand that. ´ // IT WORKS FOR MY PROBLEM Double value= 0.1d; BigDecimal total = new BigDecimal("0.0"); for (int i = 0; i < 9; i++) { total = total.add(new BigDecimal(value.toString())); } System.out.print(total); // DON'T WORK System.out.print(0.1d + 0.1d + 0.1d); // DON'T WORK System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1))); // IT WORKS System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.1")).add(new BigDecimal("0.1"))); // IT WORKS, BUT WITH LESS PRECISION System.out.println(0.1f + 0.1f + 0.1f); `Charcoal
A slight correction: "imprecision of the double" - this is technically not true. The double can perfectly represent 0.1 - with a mantissa of 1 and an exponent of -1. It's the conversion of a double to a BigDecimal that converts the floating point representation to a normal binary representation somewhere along the way. This is why the double constructor is flawed.Twedy
Um, what? No, it can't. 0.1 cannot be represented as a fraction in binary, period, which is how doubles are represented. A mantissa of 1 and an exponent of -1 gets you 0.5.Undergird
You're right, my bad. I was reading the binary representation of a double incorrectly.Twedy
C
11

First never, never use the double constructor of BigDecimal. It may be the right thing in a few situations but mostly it isn't

If you can control your input use the BigDecimal String constructor as was already proposed. That way you get exactly what you want. If you already have a double (can happen after all), don't use the double constructor but instead the static valueOf method. That has the nice advantage that we get the cannonical representation of the double which mitigates the problem at least.. and the result is usually much more intuitive.

Convalescent answered 20/3, 2012 at 23:39 Comment(0)
K
5

This is not a problem of Java, but rather a problem of computers generally. The core problem lies in the conversion from decimal format (human format) to binary format (computer format). Some numbers in decimal format are not representable in binary format without infinite repeating decimals.

For example, 0.3 decimal is 0.01001100... binary But a computer has a limited "slots" (bits) to save a number, so it cannot save all the whole infinite representation. It saves only 0.01001100110011001100 (for example). But that number in decimal is no longer 0.3, but 0.30000000000000004 instead.

Kinescope answered 20/3, 2012 at 21:45 Comment(0)
W
3

Try this:

BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1));

EDIT: Actually, looking over the Javadoc, this will have the same problem as the original. The constructor BigDecimal(double) will make a BigDecimal corresponding to the exact floating-point representation of 0.1, which is not exactly equal to 0.1.

This, however, gives the exact result, since integers CAN always be expressed exactly in floating-point representation:

BigDecimal one = new BigDecimal(1);
BigDecimal oneTenth = one.divide(new BigDecimal(10));

BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth);
Washerwoman answered 20/3, 2012 at 21:41 Comment(1)
NEVER use the double constructor for big decimal (well there may be some rare situations, but really it's a bad idea). If you can, use the string constructor (that will be exact), if you already have a double use valueOf as that way we don't get additional bogus precision...Convalescent
L
3

The problem you have is that 0.1 is represented with a slightly higher number e.g.

System.out.println(new BigDecimal(0.1));

prints

0.1000000000000000055511151231257827021181583404541015625

The Double.toString() takes into account this representation error so you don't see it.

Similarly 0.3 is represented by a value slightly lower than it really is.

0.299999999999999988897769753748434595763683319091796875

If you multiply the represented value of 0.1 by 3 you don't get the represented value for 0.3, you instead get something a little higher

0.3000000000000000166533453693773481063544750213623046875

This is not just a representation error but also a rounding error caused by the operations. This is more than the Double.toString() will correct and so you see the rounding error.

The moral of the story, if you use float or double also round the solution appropriately.

double d = 0.1 + 0.1 + 0.1;
System.out.println(d);
double d2 = (long)(d * 1e6 + 0.5) / 1e6; // round to 6 decimal places.
System.out.println(d2);

prints

0.30000000000000004
0.3
Linda answered 21/3, 2012 at 9:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.