这里的舍入误差是什么性质?
有人能帮我搞清楚这里到底发生了什么吗?
>>> 1e16 + 1.
1e+16
>>> 1e16 + 1.1
1.0000000000000002e+16
我正在使用64位的Python 2.7。首先,我认为由于浮点数的精度只有15位,所以这只是舍入误差。真正的浮点数答案可能是这样的:
10000000000000000.999999....
然后小数部分就被去掉了。但是第二个结果让我对这个理解产生了疑问,难道1不能被准确表示吗?有什么想法吗?
[编辑:为了澄清一下。我并不是在说这些答案是“错误的”。显然,它们是正确的,因为,嗯,它们确实是。我只是想理解为什么。]
4 个回答
让我们来解码一些浮点数,看看里面到底发生了什么!我将使用Common Lisp,这个语言有一个很方便的函数,可以直接获取浮点数的有效数字(也叫做尾数)和指数,而不需要去调整任何位。这里用的所有浮点数都是IEEE双精度浮点数。
> (integer-decode-float 1.0d0)
4503599627370496
-52
1
也就是说,如果我们把有效数字的值 当作一个整数来看,它是可用的最大2的幂(4503599627370496 = 2^52),然后缩小(2^-52)。(它并不是以1和指数0的形式存储,因为这样更简单,避免了有效数字左边有零,这样可以省略表示最左边的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位——只是你的输入恰好落在了最低位的边缘之外。)
10的16次方(10^16)在计算机中可以用一个叫做双精度浮点数的格式准确表示。接下来可以表示的数字是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)
这只是尽量进行四舍五入。
1e16在浮点数的十六进制表示是0x4341c37937e08000
。
1e16加2的结果是0x4341c37937e08001
。
在这个数量级上,你能表示的最小精度差是2。准确地加1.0会向下四舍五入(因为通常情况下,IEEE浮点数运算会四舍五入到最接近的偶数)。而加上大于1.0的值则会向上四舍五入到下一个可以表示的值。