You can use BigDecimal to see what the actual values are.
double a = 0.1;
double c = 0.3;
System.out.println(a + " is actually " + new BigDecimal(a));
System.out.println(c + " is actually " + new BigDecimal(c));
double a3 = a * 3;
System.out.println("0.1 * 3 or " + a3 + " is actually " + new BigDecimal(a3));
double ac = a + c;
System.out.println("0.1 + 0.3 or " + ac + " is actually " + new BigDecimal(ac));
prints
0.1 is actually 0.1000000000000000055511151231257827021181583404541015625
0.3 is actually 0.299999999999999988897769753748434595763683319091796875
0.1 * 3 or 0.30000000000000004 is actually 0.3000000000000000444089209850062616169452667236328125
0.1 + 0.3 or 0.4 is actually 0.40000000000000002220446049250313080847263336181640625
As you can see, double
cannot represent 0.1 precisely. When you print the value for 0.1 it does a small amount of rounding which hides the representation error. This is looks fine except when you perform an un-rounded calculation and you can see the representation error has accumulated.
You can see that 0.1 * 3 results in an error which is large enough to be noticeable. However 0.1 + 0.3 works out to be the same as 0.4 (This is because the error for 0.1 is slightly too large and for 0.3 its slightly too small)