关于java:令人惊讶的双重比较

surprising double comparison

本问题已经有最佳答案,请猛点这里访问。

我把代码执行结果搞砸了。

代码:

1
2
System.out.println(0.2==0.1+0.1);
System.out.println(0.3==0.1+0.1+0.1);

输出:

1
2
true
false

我知道0.2和0.3不能转换为二进制正确。

为什么我看到不同的结果?

更新:

在没有编译器的情况下,我能预测类似问题的结果吗?


1
System.out.println(0.1+0.1+0.1);

输出

1
0.30000000000000004

浮点运算存在舍入误差。有些值不能用基数2表示,不能依赖于比较浮点数。基-2中的0.1类似于基-10中的1/3。

您可以看到下面的链接

每一个计算机科学家都应该知道什么是浮点运算


您看到的事实是,浮点值不是非常精确,例如,您不应该使用==运算符比较它们。对于比较,应使用epsilon比较,即对于两个浮点值f1和f2

1
2
3
if  (Math.abs(f1 - f2) < EPSILON ) {
   // they are equal
}

其中epsilon是一些非常小的浮点值


你不能依靠==来很好地处理float数字,因为这些数字的结果不能在计算机上准确地表示,因而不可靠。当你想检查两个相等或不相等的float数字时,用fabs(a-b) < epsilon代替。

P.S.下面的测试是在C++下进行的,它给出了令人惊讶的结果(只是为了证明它是多么不可靠):

1
2
3
4
5
6
7
8
cout << (0.1==0.1) << endl;                                // true
cout << (0.2==0.1+0.1) << endl;                            // true
cout << (0.3==0.1+0.1+0.1) << endl;                        // false
cout << (0.4==0.1+0.1+0.1+0.1) << endl;                    // true
cout << (0.5==0.1+0.1+0.1+0.1+0.1) << endl;                // true
cout << (0.6==0.1+0.1+0.1+0.1+0.1+0.1) << endl;            // true
cout << (0.7==0.1+0.1+0.1+0.1+0.1+0.1+0.1) << endl;        // true        
cout << (0.8==0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1) << endl;    // false

PHP给出了:

1
2
3
4
5
6
7
8
9
10
<?php

printf("%0.20f
"
, 0.1+0.1);
printf("%0.20f
"
, 0.1+0.1+0.1);

?>
0.20000000000000001110
0.30000000000000004441

1
2
3
4
5
6
7
8
9
10
<?php
  echo 0.2==0.1+0.1?"true
"
:"false
"
;
  echo 0.3==0.1+0.1+0.1?"true
"
:"false
"
;
?>
true
false

第一个"真"的重演:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
   printf("%0.20f
"
, 0.1+0.1);
   printf("%0.20f
"
, 0.1+0.1+0.1);
   echo"
"
;
   printf("%0.20f
"
, 0.2);
   printf("%0.20f
"
, 0.3);
?>

产量

1
2
3
4
5
0.20000000000000001110
0.30000000000000004441

0.20000000000000001110
0.29999999999999998890