Unpredictable double [duplicate]

…衆ロ難τιáo~ 提交于 2019-12-01 10:08:35

问题


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 ?


回答1:


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



回答2:


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


来源:https://stackoverflow.com/questions/9667324/unpredictable-double

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!