Unpredictable double [duplicate]
Asked Answered
L

2

1

Possible Duplicate:
Double precision problems on .NET
Double calculation producing odd result

I know that internal represantation of double value 0.2 is something like 0.199999. But the following code still confuses me.

CODE:

public static void main(String[] args) {
    double d= 0.3d;
    double f= 0.1d;
    System.out.println(d+f);
    System.out.println(d*f);
    System.out.println(d);
    System.out.println(f);
    System.out.println(d-f);
    System.out.println(d/f);
    System.out.println((d-f)*(d-f));
}

OUTPUT:

0.4
0.03
0.3
0.1
0.19999999999999998
2.9999999999999996
0.039999999999999994

What is going on actually? Addition, multiplication goes well, but subtraction, division is not. Can anyone please elaborate why addition works different from subtraction ?

Licentiate answered 12/3, 2012 at 12:50 Comment(4)
has to be one of the most asked and answered questions in computing. :PMicrosporophyll
... and on StackoverflowDeil
It seems the first sentence of your question is the answer!Openwork
@Openwork My question is why addition differs from subtraction? :(Licentiate
M
4

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
Microsporophyll answered 12/3, 2012 at 12:53 Comment(4)
Thanks a lot :) why is that 0.1 has more and 0.3 less that its actuals?Licentiate
0.1 is slightly more as this is the closest representable value. For 0.3 the closest representable value is slightly less.Microsporophyll
OMG! Why is that :( What does closest actually mean?Licentiate
See my last edit for the two values closest to 0.1 double can represent. You can see the one it uses is the closest. i.e. it has the least error.Microsporophyll
V
5

If you're desperate for precision use BigDecimal.

public static void main(String[] args) {
    BigDecimal d = BigDecimal.valueOf(0.3d);
    BigDecimal f = BigDecimal.valueOf(0.1d);
    System.out.println(d.add(f));
    System.out.println(d.multiply(f));
    System.out.println(d);
    System.out.println(f);
    System.out.println(d.subtract(f));
    System.out.println(d.divide(f));
    System.out.println((d.subtract(f)).multiply(d.subtract(f)));
}

Output

0.4
0.03
0.3
0.1
0.2
3
0.04

Or round your result, DecimalFormat will do this quite nicely using the # symbol meaning only show decimals where necessary

    double d = 0.3d;
    double f = 0.1d;
    DecimalFormat format = new DecimalFormat("#.##");
    System.out.println(format.format(d + f));
    System.out.println(format.format(d * f));
    System.out.println(format.format(d));
    System.out.println(format.format(f));
    System.out.println(format.format(d - f));
    System.out.println(format.format(d / f));
    System.out.println(format.format((d - f) * (d - f)));

Output

0.4
0.03
0.3
0.1
0.2
3
0.04
Vivle answered 12/3, 2012 at 12:57 Comment(0)
M
4

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
Microsporophyll answered 12/3, 2012 at 12:53 Comment(4)
Thanks a lot :) why is that 0.1 has more and 0.3 less that its actuals?Licentiate
0.1 is slightly more as this is the closest representable value. For 0.3 the closest representable value is slightly less.Microsporophyll
OMG! Why is that :( What does closest actually mean?Licentiate
See my last edit for the two values closest to 0.1 double can represent. You can see the one it uses is the closest. i.e. it has the least error.Microsporophyll

© 2022 - 2024 — McMap. All rights reserved.