不可预测的双倍 [英] Unpredictable double

查看:148
本文介绍了不可预测的双倍的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


可能重复:

.NET上的双精度问题

双重计算产生奇数结果


我知道双重值的内部代表 0.2 类似于 0.199999 。但以下代码仍然让我感到困惑。



代码:

  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

实际上发生了什么?除此之外,乘法运算顺利,但减法,除法不是。任何人都可以详细说明为什么添加与减法不同

解决方案

简短的答案是你有一个表示错误和浮点运算的舍入误差。 toString()知道关于表示错误,所以如果没有四舍五入错误,你看不到它。但是,如果舍入误差太大,您可以这样做。



解决方案是使用BigDecimal或舍弃您的结果。






如果您使用BigDecimal,它将显示您真正拥有的确切值。

  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)));

打印

  D = 0.299999999999999988897769753748434595763683319091796875 
F = 0.1000000000000000055511151231257827021181583404541015625
D + F = 0.40000000000000002220446049250313080847263336181640625
0.4 = 0.40000000000000002220446049250313080847263336181640625
D * F = 0.0299999999999999988897769753748434595763683319091796875
0.03 = 0.0299999999999999988897769753748434595763683319091796875
DF = 0.1999999999999999833466546306226518936455249786376953125
0.2 = 0.200000000000000011102230246251565404236316680908203125
D / F = 2.999999999999999555910790149937383830547332763671875
(DF)*(DF)= 0.03999999999999999389377336456163902767002582550048828125

你会注意到 0.1 稍微过大,而 0.3 稍微太小了。这意味着当您添加或乘法时,您将获得一个关于正确的数字。但是,如果您使用减法或除数,则错误累积,您会得到一个距离所表示的数字太远的数字。



您可以看到0.1和0.3的结果与0.4相同,而0.3 - 0.1不会导致与0.2相同的值。




BTW来舍弃答案而不使用BigDecimal,您可以使用

  System.out.printf(df = .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));

打印

  df = 0.20 
d / f = 3.00
(df)*(df)= 0.04

  System.out.println(df =+ 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;
}

打印

  System.out.println(df =+ roundTo6Places(d  -  f)); 
System.out.println(d / f =+ roundTo6Places(d / f));
System.out.println((d-f)*(d-f)=+ roundTo6Places((d - f)*(d - f)));

舍入删除舍入错误(仅留下toString设计的表示错误) / p>




0.1之前和之后可以表示的值可以计算为

  double before_f = Double.longBitsToDouble(Double.doubleToLongBits(f) -  1); 
System.out.println(0.1之前的值是+ new BigDecimal(before_f)+error =+ BigDecimal.valueOf(0.1).subtract(new BigDecimal(before_f)));
System.out.println(0.1之后的值是+ new BigDecimal(f)+error =+ new BigDecimal(f).subtract(BigDecimal.valueOf(0.1)));

打印

  0.1之前的值为0.099999999999999167332731531132594682276248931884765625 
error = 8.32667268468867405317723751068115234375E-18
0.1之后的值为0.1000000000000000055511151231257827021181583404541015625
error = 5.5511151231257827021181583404541015625E-18


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 ?

解决方案

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

这篇关于不可预测的双倍的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆