14. 浮点数运算:问题和局限

浮点数在计算机硬件中表示为基地 2 (二进制) 分数。例如,小数部分

0.125

有 1/10 + 2/100 + 5/1000,和相同的方式二进制小数部分的值

0.001

具有值 0/2 + 0/4 + 1/8。这两个分数有相同的值,唯一真正的区别是,第一次写在基地 10 分数表示法和基地 2 中的第二。

不幸的是,大多数十进制分数不能完全作为二进制分数表示。结果是,一般情况下,你输入的十进制浮点数以近似的二进制浮点数值存储在计算机中。

问题是容易理解在基地 10 中的第一个。考虑分数 1/3。你可以近似,作为基地 10 分数:

0.3

或更好,

0.33

或更好,

0.333

等等。无论你愿意写下来的多少位数字,结果永远不会确切地 1/3,但将会越来越多地更好地逼近的 1/3。

方式相同,无论多少基地 2 位数你愿意使用,无法确切地为基地 2 分数表示的十进制值 0.1。在基地 2,1/10 是无限重复的分数

0.0001100110011001100110011001100110011001100110011...

在任何有限数量的位,停下来,你得到近似值。

在典型的机器运行 Python 上, 供 Python 的浮有 53 位的精度,所以内部存储的值时,您输入的十进制数0.1是二进制分数

0.00011001100110011001100110011001100110011001100110011010

这就是接近,但不完全等于 1/10。

它很容易忘了存储的值是一个近似原始的小数部分,浮游物口译员提示符处显示的方式。Python 只打印的真正的十进制值的机器所存储的二进制逼近一个十进制近似。如果要打印的真正的十进制值为 0.1 存储的二进制逼近的 Python,它会显示

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

这就是比大多数人更多位数找到有用的所以 Python 保留的位数易于管理通过显示舍入的值改为

>>> 0.1
0.1

它是重要的是要意识到这是,真正的意义上说,一种错觉: 这台机器中的值不是确切地 1/10,你只需去追捕显示真实机价值。这一事实变得明显,一旦你尝试这样做算术运算,这些值

>>> 0.1 + 0.2
0.30000000000000004

请注意,这是在二进制浮点的本质: 这不是一个 bug 在 Python,和它也不是你代码中的 bug。您会看到同样的东西在所有支持的语言,您的硬件浮点算术 (虽然有些语言可能不显示差异,默认情况下,或在所有输出模式)。

从这一个按照其他的惊喜。例如,如果您尝试以圆值 2.675 到两位小数,你这

>>> round(2.675, 2)
2.67

内置round()函数的文档说它舍入到最接近的值舍入为零的联系。因为小数部分 2.675 是 2.67 和 2.68 确切地中间,你可能预计的结果在这里 (一个二进制近似) 2.68。但是不是的,因为当十进制字符串2.675转换为一个二进制浮点数时,它仍然被替换为一个二进制的近似值,其确切的值是

2.67499999999999982236431605997495353221893310546875

因为这个近似值稍微接近 2.67 而不是 2.68,所以向下舍入。

如果你的情况需要考虑十进制的中位数是如何被舍入的,你应该考虑使用decimal模块。顺便说一下,decimal模块还提供了很好的方式可以“看到”任何 Python 浮点数的精确值。

>>> from decimal import Decimal
>>> Decimal(2.675)
Decimal('2.67499999999999982236431605997495353221893310546875')

另一个结果是,因为 0.1 不是精确的 1/10,十个值为 0.1 数相加可能也不会正好是 1.0:

>>> sum = 0.0
>>> for i in range(10):
...     sum += 0.1
...
>>> sum
0.9999999999999999

二进制浮点数计算有很多这样意想不到的结果。“0.1”的问题在下面"误差的表示"一节中有准确详细的解释。更完整的常见怪异现象请参见浮点数的危险

最后我要说,“没有简单的答案”。也不要过分小心浮点数!Python 浮点数计算中的误差源之于浮点数硬件,大多数机器上每次计算误差不超过 2**53 分之一。对于大多数任务这已经足够了,但是你要在心中记住这不是十进制算法,每个浮点数计算可能会带来一个新的舍入错误。

虽然确实有问题存在,对于大多数平常的浮点数运算,你只要简单地将最终显示的结果舍入到你期望的十进制位数,你就会得到你期望的最终结果。关于如何精确控制浮点数的显示请参阅格式化字符串的语法str.format()方法的格式说明符。

14.1. 二进制表示的误差

这一节将详细解释“0.1”那个示例,并向你演示对于类似的情况自已如何做一个精确的分析。假设你已经基本了解浮点数的二进制表示。

二进制表示的误差 指的是这一事实,一些(实际上是大多数) 十进制小数不能精确地用二进制小数表示。这是为什么 Python(或者 Perl、 C、 C++、 Java、 Fortran和其他许多语言)通常不会显示你期望的精确的十进制数的主要原因:

>>> 0.1 + 0.2
0.30000000000000004

这是为什么?1/10 和 2/10 不能用二进制小数精确表示。今天(2010 年 7 月)几乎所有的机器都使用 IEEE-754 浮点数算法,几乎所有的平台都将 Python 的浮点数映射成 IEEE-754“双精度浮点数”。754 双精度浮点数包含 53 位的精度,所以输入时计算机努力将 0.1 转换为最接近的 J/2**N 形式的小数,其中J 是一个 53 位的整数。改写

1 / 10 ~= J / (2**N)

J ~= 2**N / 10

回想一下 J 有 53 位(>= 2**52< 2**53),所以 N 的最佳值是 56:

>>> 2**52
4503599627370496
>>> 2**53
9007199254740992
>>> 2**56/10
7205759403792793

即 56 是 N  保证 J 具有 53 位精度的唯一可能的值。J 可能的最佳值是商的舍入:

>>> q, r = divmod(2**56, 10)
>>> r
6

由于余数大于 10 的一半,最佳的近似值是向上舍入:

>>> q+1
7205759403792794

因此在 754 双精度下 1/10 的最佳近似是 J 取大于 2**56 的那个数,即

7205759403792794 / 72057594037927936

请注意由于我们向上舍入,这其实有点大于 1/10;如果我们没有向上舍入,商数就会有点小于 1/10。但在任何情况下它都不可能是精确的 1/10!

所以计算机从来没有"看到"1/10: 它看到的是上面给出的精确的小数,754 双精度下可以获得的最佳的近似了:

>>> .1 * 2**56
7205759403792794.0

如果我们把这小数乘以 10**30,我们可以看到其(截断后的)值的最大 30 位的十进制数:

>>> 7205759403792794 * 10**30 // 2**56
100000000000000005551115123125L

也就是说存储在计算机中的精确数字约等于十进制值 0.100000000000000005551115123125。以前 Python 2.7 和 Python 3.1 版本中,Python 四舍五入到 17 个有效位,给出的值是 '0.10000000000000001'。在当前版本中,Python 显示一个最短的十进制小数,它会正确舍入真实的二进制值,结果就是简单的‘0.1’。