实数并不真实#
说实数不是真实的,不仅仅是一个文字游戏,而是一个计算现实。让我们做一个简单的实验:在你最喜欢的数字处理工具中尝试以下操作。在Excel中:
=IF(1+1E-016 = 1,1,0)
将打印 1。在 Python 中:
>>> 1 == 1+1e-16
True
在C语言中,代码
#include<stdio.h>
int main(void)
{
if (1+1e-16 == 1) printf("True\n");
else printf("False\n");
return 0;
}
将打印 True
。在 R 中:
> 1 == 1+1e-16
[1] TRUE
请注意,这种行为不仅限于小数字;它也发生在较大的数字上。例如:
>>> 1+1e16 == 1e16
True
这表明结果的精度取决于所涉及数字的相对比例。
尽管这种行为很典型,但也有一些例外。其中之一是 GNU-bc 命令行工具:
> bc
1.0 == 1.0+10^(-16)
1
scale=20
1.0 == 1.0+10^(-16)
0
1.0 == 1.0+10^(-21)
1
当我们设置scale
参数为\(20\)时,代码能够识别出数字是不同的。但这只是提高了门槛;bc
仍然无法识别最后两个数字之间的差异。另一个允许扩展甚至无限(取决于内存)精度的库是GNU Multiple Precision Arithmetic Library,但其细节超出了本文档的范围。
这些失败的原因是计算机必须将数字存储为位序列,而大多数常见实现遵循IEEE 754标准。特别是,IEEE-754为双精度格式设定了标准。这个标准如此普遍,以至于几乎所有计算机都有专门的硬件来提高以这种方式表示的数字的操作性能。一个后果是,对替代扩展数字表示形式的数学操作往往比遵循IEEE 754标准表示的数字的操作要慢得多。10倍甚至100倍的性能下降是常见的。
由于硬件对双精度算术的支持所获得的性能,Gurobi 依赖于这一标准(大多数软件也是如此)。然而,这种速度是有代价的:计算结果通常与数学所规定的不同。例如,结合律(a + (b + c) = (a + b) + c
)是算术的基本属性,但双精度算术在(Python 中)给出:
>>> (1+1e-16)+1e-16 == 1 + (1e-16 + 1e-16)
False
此外,许多常见的数字(例如0.1)无法精确表示。
因此,像两个数字是否相等、一个数字是否等于零、或者一个数字是否是整数这样的简单问题,在使用浮点运算时可能会变得相当复杂。