12

有人可以帮我解开这里到底发生了什么吗?

>>> 1e16 + 1.
1e+16
>>> 1e16 + 1.1
1.0000000000000002e+16

我在 64 位 Python 2.7 上。首先,我假设因为浮点数的精度只有 15,所以它只是舍入误差。真正的浮点答案可能类似于

10000000000000000.999999....

小数点就被删掉了。但第二个结果让我质疑这种理解,不能准确表示 1 吗?有什么想法吗?

[编辑:只是为了澄清。我绝不是在暗示答案是“错误的”。显然,他们是对的,因为他们是对的。我只是想了解原因。]

4

4 回答 4

10

它只是尽可能地四舍五入。

浮动十六进制的 1e16 是0x4341c37937e08000.

1e16+2 是0x4341c37937e08001.

在这个数量级上,您可以表示的最小精度差异是 2。加上 1.0 会精确舍入(因为通常 IEEE 浮点数学会舍入为偶数)。添加大于 1.0 的值将向上舍入到下一个可表示的值。

于 2013-02-25T03:56:53.467 回答
5

10^16 = 0x002386f26fc10000 完全可以表示为双精度浮点数。下一个可表示的数字是 1e16+2。1e16+1 正确舍入为 1e16,1e16+1.1 正确舍入为 1e16+2。检查此 C 程序的输出:

#include <stdio.h>
#include <math.h>
#include <stdint.h>

int main()
{
  uint64_t i = 10000000000000000ULL;
  double a = (double)i;
  double b = nextafter(a,1.0e20); // next representable number
  printf("I=0x%016llx\n",i); // 10^16 in hex
  printf("A=%a (%.4f)\n",a,a); // double representation
  printf("B=%a (%.4f)\n",b,b); // next double
}

输出:

I=0x002386f26fc10000
A=0x1.1c37937e08p+53 (10000000000000000.0000)
B=0x1.1c37937e08001p+53 (10000000000000002.0000)
于 2013-02-25T04:01:00.247 回答
4

让我们解码一些浮点数,看看实际发生了什么!我将使用 Common Lisp,它有一个方便的函数来获取浮点数的有效位(又名尾数)和指数,而无需旋转任何位。使用的所有浮点数都是 IEEE 双精度浮点数。

> (integer-decode-float 1.0d0)
4503599627370496
-52
1

也就是说,如果我们将存储在有效数字中的值视为整数,则它是可用的 2 的最大幂 (4503599627370496 = 2^52),按比例缩小 (2^-52)。(它不存储为指数为 0 的 1,因为有效数字的左侧永远不会有零更简单,这允许我们跳过表示最左边的 1 位并具有更高的精度。不是这种形式的数字被称为不正常的。)

让我们看看1e16。

> (integer-decode-float 1d16)
5000000000000000
1
1

这里我们有表示 (5000000000000000) * 2^1。请注意,尽管有效数字是一个很好的四舍五入数,但它不是 2 的幂;这是因为 1e16 不是 2 的幂。每次乘以 10,就是乘以 2 和 5;乘以 2 只是增加指数,但乘以 5 是“实际”乘法,这里我们乘以 5 16 次。

5000000000000000 = 10001110000110111100100110111111000001000000000000000 (base 2)

请注意,这是一个 53 位二进制数,因为双浮点数具有 53 位有效数。

但是理解这种情况的关键是指数是 1。(指数很小表明我们正在接近精度的极限。)这意味着浮点值是 2^1 = 2 倍这个有效数。

现在,当我们试图表示这个数字加 1 时会发生什么?好吧,我们需要以相同的比例表示 1。但是我们可以对这个数字做出的最小改变恰好是 2,因为有效数字的最低有效位的值为 2!

也就是说,如果我们增加有效位,使最小的变化,我们得到

5000000000000001 = 10001110000110111100100110111111000001000000000000001 (base 2)

当我们应用指数时,我们得到 2 * 5000000000000001 = 10000000000000002,这正是您观察到的值。您只能拥有 10000000000000000 或 10000000000000002,而 10000000000000001.1 更接近于后者。

(请注意,这里的问题甚至不是十进制数在二进制中不精确!这里没有二进制“重复小数”,有效数字的右端有很多 0 位 - 只是你的输入整齐地下降刚好超过最低位。)

于 2013-02-25T04:13:38.580 回答
3

使用 numpy,您可以看到下一个越来越小的可表示 IEEE 浮点数:

>>> import numpy as np
>>> huge=1e100
>>> tiny=1e-100
>>> np.nextafter(1e16,huge)
10000000000000002.0
>>> np.nextafter(1e16,tiny)
9999999999999998.0

所以:

>>> (np.nextafter(1e16,huge)-np.nextafter(1e16,tiny))/2.0
2.0

和:

>>> 1.1>2.0/2
True

因此 1e16 + 1.1 正确舍入为下一个更大的 IEEE 可表示数字 10000000000000002.0

原样:

>>> 1e16+1.0000000000000005
1.0000000000000002e+16

和 1e16-(略大于 1)向下舍入 2 到下一个较小的 IEEE 数字:

>>> 1e16-1.0000000000000005
9999999999999998.0

请记住,32 位与 64 位 Python 是无关紧要的。重要的是所使用的IEEE 格式的大小。还要记住,数字的幅度越大,epsilon 值(两个下一个较大和较小的 IEEE 值之间的差值基本上)会发生变化

您也可以看到这一点:

>>> def f_to_bits(f): return struct.unpack('<Q', struct.pack('<d', f))[0]
... 
>>> def bits_to_f(bits): return struct.unpack('<d', struct.pack('<Q', bits))[0]
... 
>>> bits_to_f(f_to_bits(1e16)+1)
1.0000000000000002e+16
>>> bits_to_f(f_to_bits(1e16)-1)
9999999999999998.0
于 2013-02-25T04:32:42.760 回答