7

我最近正在阅读有关在内存中存储浮点值的内容。我写了一个小程序来测试我读过的内容。我注意到 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

4 回答 4

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
于 2012-10-31T14:48:47.550 回答
6

当“0.90”转换为double时,结果是0.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,没有理由期望它们是一样的。

(正如它在这种情况下发生,例如0是.00000000000000002220446049250313080847263336181640625和e 1是.000000000000000088817841970012523233890533447265625。此外,减去2只引入了没有新的误差1.1,的“1.1”转换为加倍,由Sterbenz引理后)。

额外细节:

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

于 2012-10-31T20:27:13.497 回答
2

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

嗯,这就是你的答案(或者更准确地说,正如 Mark Byers 所指出的,一些十进制值不能完全表示为双精度)!0.9 或 1.1 都不能表示为双精度数,因此会出现舍入错误。

您可以使用 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
于 2012-10-31T14:49:50.300 回答
1

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

于 2012-10-31T14:49:40.363 回答