开发者

Java doubles adding strangely (and no, it's not for money)

So this is the only relevant section of the code

System.out.println("first term of " + firstTerm +
                   " second term of " + secondTerm + 
                   " third term of " + finalTermHolder + 
                   " should equal " + oppositeIntHolder);
double holder = firstTerm + secondTerm + finalTermHolder;
System.out.println(holder + " should equal " + oppositeIntHolder);

This is uninterrupted code, there is nothing in between these. The output for the first println is:

first term of 2.5147186257614296 second term of -9.514718625761429 third term of 7.0 should equal 0.0

The second println results in:

8.881784197001252E-16 should e开发者_开发技巧qual 0.0

Why are -9.5, 2.5, and 7 adding up to 8.9 instead of 0?


they are not adding up to 8.9. they are adding up to 8.9e-16. that's something like 0.00000000000000089

even if the numbers were displayed as -9.5 etc, you might still see this. it is because binary computers do not store decimals exactly. small errors occur. and yes, this is exactly the problem that happens with money.


8.881784197001252E-16 is a lot closer to zero than you think ; )

Double in Java is a floating point number. IF you're looking for an exact representation of the number try using BigDecimal instead of Double

BigDecimal num1 = new BigDecimal(3.32);
BigDecimal num2 = new BigDecimal(3.68);
System.out.println(num1.add(num2)); //will output 7.0


When you perform double operations you need to provide appropriate rounding. Even for BigDecimal division you need to provide appropriate rounding.

For printing double a small amount of rounding is done so you don't see the representation error. However after a few calculations (only one is needed) you the rounding error is too large and you can see the error.

If you want to see the representation and rounding error use BigDecimal as it does an exact conversion from double. Something which can be surprising in itself.

BTW, you won't get a rounding error with simple powers of 2. so -9.5 + 2.5 + 7.0 will always be 0.0. You only get rounding error with other decimals like 0.1

double[] ds = {
        0.1,
        0.2,
        -0.3,
        0.1 + 0.2 - 0.3};
for (double d : ds) {
    System.out.println(d + " => " + new BigDecimal(d));
}

prints

0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 => 5.5511151231257827021181583404541015625E-17

You can see that the representation for 0.1 and 0.2 is slightly higher than those values, and -0.3 is also slightly higher. When you print them, you get the nicer 0.1 instead of the actual value represented 0.1000000000000000055511151231257827021181583404541015625

However, when you add these values together, you get a value which is slightly higher than 0.

To resolve this issue, you need to provide appropriate rounding. With money this is easy as you know how many decimal places are appropriate and unless you have $70 trillion you won't get a rounding error large enough you cannot correct it.

public static double roundToTwoPlaces(double d) {
    return ((long) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5)) / 100.0;
}

If you add this into the result, there is still a small representation error, however it is not large enough that the Double.toString(d) cannot correct for it.

double[] ds = {
        0.1,
        0.2,
        -0.3,
        0.1 + 0.2 - 0.3};
for (double d : ds) {
    System.out.println(d + " to two places " + roundToTwoPlaces(d) + " => " + new BigDecimal(roundToTwoPlaces(d)));
}

prints

0.1 to two places 0.1 => 0.1000000000000000055511151231257827021181583404541015625
0.2 to two places 0.2 => 0.200000000000000011102230246251565404236316680908203125
-0.3 to two places -0.3 => -0.299999999999999988897769753748434595763683319091796875
5.551115123125783E-17 to two places 0.0 => 0
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜