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