为什么两个浮点结果在名义上具有相同的值时会有所不同?

时间:2012-10-31 14:46:25

标签: java floating-point

我最近在阅读有关在内存中存储浮点值的内容。我写了一个小程序来测试我读过的内容。我注意到Java处理浮点值的方式有所不同。

public class Test
{
   public static void main(String args[])
   {
     double a = 0.90;
     System.out.println(a);
     System.out.println(2.00-1.10);
   }
 }

以上程序正在打印

0.9
0.8999999999999999

为什么这两个语句都没有打印相同的值?我知道一些浮动值无法准确表示。在这种情况下,两者都应该给出相同的值。

4 个答案:

答案 0 :(得分:8)

  

为什么这两个语句都没有打印相同的值?

结果不一样。

  

我知道一些浮动值无法准确表示。

因此,您应该假设操作的结果可能取决于您使用的值的表示错误量。

for (long l = 1; l <= 1e16; l *= 10) {
    double a = l + 2;
    double b = l + 1.1;
    System.out.println(a + " - " + b + " is " + (a - b));
}

随着值变大,表示错误增加并且与0.9

的结果相比变大
3.0 - 2.1 is 0.8999999999999999
12.0 - 11.1 is 0.9000000000000004
102.0 - 101.1 is 0.9000000000000057
1002.0 - 1001.1 is 0.8999999999999773
10002.0 - 10001.1 is 0.8999999999996362
100002.0 - 100001.1 is 0.8999999999941792
1000002.0 - 1000001.1 is 0.9000000000232831
1.0000002E7 - 1.00000011E7 is 0.900000000372529
1.00000002E8 - 1.000000011E8 is 0.9000000059604645
1.000000002E9 - 1.0000000011E9 is 0.8999999761581421
1.0000000002E10 - 1.00000000011E10 is 0.8999996185302734
1.00000000002E11 - 1.000000000011E11 is 0.899993896484375
1.000000000002E12 - 1.0000000000011E12 is 0.9000244140625
1.0000000000002E13 - 1.00000000000011E13 is 0.900390625
1.00000000000002E14 - 1.000000000000011E14 is 0.90625
1.000000000000002E15 - 1.0000000000000011E15 is 0.875
1.0000000000000002E16 - 1.0000000000000002E16 is 0.0

并且关于当表示错误变得如此之大时,你的操作什么都不做的主题。

for (double d = 1; d < Double.MAX_VALUE; d *= 2) {
    if (d == d + 1) {
        System.out.println(d + " + 1 == " + (d + 1));
        break;
    }
}
for (double d = 1; d < Double.MAX_VALUE; d *= 2) {
    if (d == d - 1) {
        System.out.println(d + " - 1 == " + (d - 1));
        break;
    }
}

打印

9.007199254740992E15 + 1 == 9.007199254740992E15
1.8014398509481984E16 - 1 == 1.8014398509481984E16

答案 1 :(得分:6)

当“0.90”转换为double时,结果为.9加上一些小错误,e 0 。因此a等于.9 + e 0

当“1.10”转换为double时,结果为1.1加上一些小错误,e 1 ,因此结果为1.1 + e 1

这两个错误,e 0 和e 1 ,通常彼此无关。简单地说,不同的十进制数与二进制浮点数的距离不同。评估2.00-1.10时,结果为2-(1.1 + e 1 )=。9-e 1 。所以你的一个数字是.9 + e 0 ,另一个是.9-e 1 ,没有理由期望它们是相同的。< / p>

(在这种情况下,e 0 是.00000000000000002220446049250313080847263336181640625,e 1 是.000000000000000088817841970012523233890533447265625。另外,从2减去1.1不会引入新的错误Sterbenz'Lemma将“1.1”转换为double。)

其他详情:

在二进制文件中,.9 .11100110011001100110011001100110011001100110011001100 11001100 ... 粗体中的位符合双精度格式。尾随位不适合,因此该数字在该点处舍入。这导致.9的精确值与表示为double的“.9”的值之间存在差异。在二进制中,1.1 1.00011001100110011001100110011001100110011001 10011001 ...再次,该数字是四舍五入的。但观察量舍入是不同的。对于.9,1100 1100 ...被舍入到1 0000 0000 ...,在该位置增加了00110011 ....对于1.1,将1001 1001向上舍入为1 0000 0000 ...,在该位置添加01100110 ...(并以粗体位表示进位)。这两个职位不同; 1.1从小数点的左边开始,所以看起来像这样:1。[这里是52位] [发生舍入的地方]。 .9从小数点的右边开始,所以看起来像这样:。[这里是53位] [发生舍入的地方]。所以1.1的舍入,除了01100110 ...而不是00110011 ...,也加倍,因为它出现在.9舍入的左边一位。所以你有两个效果使得e 0 与e 1 不同:舍入的尾随位是不同的,并且舍入发生的位置是不同的。

答案 2 :(得分:1)

你的理由是,即使0.9不能用double精确表示,它应该具有与2.0 - 1.1完全相同的double值,因此导致相同的打印值。这就是错误 - 这个减法不会产生由“0.9”(或精确值0.9)表示的double

答案 3 :(得分:1)

  

我知道一些浮动值无法准确表示

那是你的答案(或者更准确地说,正如马克·拜尔斯所指出的,一些十进制值不能完全表示为双精度)! 0.9或1.1都不能表示为double,因此您会出现舍入错误。

您可以使用BigDecimal检查各种双打的确切值:

public static void main(String args[]) {
    double a = 0.9d;
    System.out.println(a);
    System.out.println(new BigDecimal(a));
    double b = 2d - 1.1d;
    System.out.println(b);
    System.out.println(new BigDecimal(2.0d));
    System.out.println(new BigDecimal(1.1d));
    System.out.println(new BigDecimal(b));
}

输出:

0.9
0.90000000000000002220446049250313080847263336181640625
0.8999999999999999
2
1.100000000000000088817841970012523233890533447265625
0.899999999999999911182158029987476766109466552734375