The short answer is you have a representation error and a rounding error for floating point operations. The toString()
"knows" about the representation error so if there is no rounding error you don't see it. But if the rounding error is too large, you do.
The solution is to either use BigDecimal or round your result.
If you use BigDecimal it will show the exact values you really have.
double d = 0.3d;
double f = 0.1d;
System.out.println("d= " + new BigDecimal(d));
System.out.println("f= " + new BigDecimal(f));
System.out.println("d+f= " + new BigDecimal(d + f));
System.out.println("0.4= " + new BigDecimal(0.4));
System.out.println("d*f= " + new BigDecimal(d * f));
System.out.println("0.03= " + new BigDecimal(0.03));
System.out.println("d-f= " + new BigDecimal(d - f));
System.out.println("0.2= " + new BigDecimal(0.2));
System.out.println("d/f= " + new BigDecimal(d / f));
System.out.println("(d-f)*(d-f)= " + new BigDecimal((d - f) * (d - f)));
prints
d= 0.299999999999999988897769753748434595763683319091796875
f= 0.1000000000000000055511151231257827021181583404541015625
d+f= 0.40000000000000002220446049250313080847263336181640625
0.4= 0.40000000000000002220446049250313080847263336181640625
d*f= 0.0299999999999999988897769753748434595763683319091796875
0.03= 0.0299999999999999988897769753748434595763683319091796875
d-f= 0.1999999999999999833466546306226518936455249786376953125
0.2= 0.200000000000000011102230246251565404236316680908203125
d/f= 2.999999999999999555910790149937383830547332763671875
(d-f)*(d-f)= 0.03999999999999999389377336456163902767002582550048828125
You will notice that 0.1
is slightly too large and 0.3
is slightly too small. This means that when you add or multiply them you get a number which is about right. However if you use subtract or division, the errors accumulate and you get a number which is too far from the represented number.
i.e. you can see that 0.1 and 0.3 results in the same value as 0.4, whereas 0.3 - 0.1 doesn't result in the same value as 0.2
BTW to round the answer without using BigDecimal you can use
System.out.printf("d-f= %.2f%n", d - f);
System.out.printf("d/f= %.2f%n", d / f);
System.out.printf("(d-f)*(d-f)= %.2f%n", (d - f) * (d - f));
prints
d-f= 0.20
d/f= 3.00
(d-f)*(d-f)= 0.04
or
System.out.println("d-f= " + roundTo6Places(d - f));
System.out.println("d/f= " + roundTo6Places(d / f));
System.out.println("(d-f)*(d-f)= " + roundTo6Places((d - f) * (d - f)));
public static double roundTo6Places(double d) {
return (long)(d * 1e6 + (d > 0 ? 0.5 : -0.5)) / 1e6;
}
prints
System.out.println("d-f= " + roundTo6Places(d - f));
System.out.println("d/f= " + roundTo6Places(d / f));
System.out.println("(d-f)*(d-f)= " + roundTo6Places((d - f) * (d - f)));
The rounding removes the rounding error (leaving only the representation error which the toString is designed to handle)
The value which can be represented before and after 0.1 can be calculated as
double before_f = Double.longBitsToDouble(Double.doubleToLongBits(f) - 1);
System.out.println("The value before 0.1 is " + new BigDecimal(before_f) + " error= " + BigDecimal.valueOf(0.1).subtract(new BigDecimal(before_f)));
System.out.println("The value after 0.1 is " + new BigDecimal(f) + " error= " + new BigDecimal(f).subtract(BigDecimal.valueOf(0.1)));
prints
The value before 0.1 is 0.09999999999999999167332731531132594682276248931884765625
error= 8.32667268468867405317723751068115234375E-18
The value after 0.1 is 0.1000000000000000055511151231257827021181583404541015625
error= 5.5511151231257827021181583404541015625E-18