2

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 equal 0.0

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

Josh Crozier
  • 202,159
  • 50
  • 343
  • 273
Nathan
  • 41
  • 3
  • 1
    It's not adding up to 8.9, it's adding up to .00000000000000089 – John Flatness Aug 17 '11 at 01:23
  • 1
    It's 8.9E-16, which is not the same as 8.9. Actually, 8.9E-16 = 0.00000000000000089, which is the same as 0 in floating point calculations due to approximations. – Bruno Reis Aug 17 '11 at 01:23
  • http://en.wikipedia.org/wiki/Floating_point has a good treatise with further more involved links if you are truly interested. – Romain Hippeau Aug 17 '11 at 01:26
  • I was actually aware of that error and scientific notation, just blanked on what the E meant. Sorry about the temporary lapse in intelligence. Anyways, revised the question. – Nathan Aug 17 '11 at 01:31
  • I nominate this question and all its crazy edits for the stackoverflow question of the day award. – Hovercraft Full Of Eels Aug 17 '11 at 01:46
  • i rolled back the question to the original. you don't get to ask a completely different one because you made a mistake first time round (especially when the second is a mess too). if you want to ask something else, create a new question. – andrew cooke Aug 17 '11 at 01:49
  • I agree with andrew in theory, that if you have a new question you create a new thread. Still Andrew, you've removed the crazily beautiful schizophrenia that all the edits caused. – Hovercraft Full Of Eels Aug 17 '11 at 01:50
  • I don't....get...to ask a different question? Okay, I'll make a new thread next time. – Nathan Aug 20 '11 at 00:30

3 Answers3

4

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.

andrew cooke
  • 42,329
  • 8
  • 83
  • 138
  • The problem being due to conversion between base 10 and base 2? – James P. Aug 17 '11 at 01:27
  • 1
    yeah. some things work fine (actually ".5" *is* ok in binary, so the particular values here should work...) but others (eg ".1") don't have a finite representation in binary. – andrew cooke Aug 17 '11 at 01:28
2

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
Shawn
  • 6,855
  • 6
  • 29
  • 44
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
Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075