14. 浮点算法:问题和局限性

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

0.125

具有值 1/10 2/100 5/1000,并且以同样的方式,二进制分数

0.001

值是 0/2 0/4 1/8.这两个分数具有相同的值,唯一的实际区别是,第一个分数以 10 为基数的分数表示,第二个分数以 2 为基数。

不幸的是,大多数十进制分数不能完全表示为二进制分数。结果是,通常,您 Importing 的十进制浮点数仅由计算机中实际存储的二进制浮点数近似。

首先从 10 开始更容易理解该问题。考虑分数 1/3.您可以将其近似为以 10 为基数的分数:

0.3

or, better,

0.33

or, better,

0.333

等等。无论您愿意写下多少个数字,结果都永远不会是精确的 1/3,而是会越来越好地逼近 1/3.

同样,无论您愿意使用多少个 2 位数字,十进制值 0.1 都不能精确表示为 2 位小数。以 2 为底的 1/10 是无限重复的分数

0.0001100110011001100110011001100110011001100110011...

停在任意有限的位数上,就可以得出一个近似值。

在运行 Python 的典型计算机上,Python 浮点数可使用 53 位精度,因此 Importing 十进制数0.1时内部存储的值是二进制分数

0.00011001100110011001100110011001100110011001100110011010

接近但不完全等于 1/10.

很容易忘记存储的值是原始十进制小数的近似值,因为浮点数在解释器提示符下显示的方式。 Python 仅将十进制近似值打印到机器存储的二进制近似值的真实十进制值。如果 Python 要打印存储为 0.1 的二进制近似值的真实十进制值,则必须显示

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

这比大多数人认为有用的数字还要多,因此 Python pass显示舍入的值来保持数字的可 Management 性。

>>> 0.1
0.1

重要的是要意识到,这实际上是一种错觉:机器中的值不完全是 1/10,您只是舍入真实机器值的显示。您try对这些值进行算术运算后,这一事实就变得显而易见

>>> 0.1 + 0.2
0.30000000000000004

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

此后还有其他惊喜。例如,如果您try将值 2.675 舍入到小数点后两位,您将得到

>>> round(2.675, 2)
2.67

内置round()函数的文档说它四舍五入到最接近的值,四舍五入关系从零开始。由于十进制小数 2.675 恰好位于 2.67 和 2.68 之间的一半,因此您可能希望此处的结果是 2.68(二进制近似值)。并非如此,因为当十进制字符串2.675转换为二进制浮点数时,它再次被二进制近似替换,其精确值为

2.67499999999999982236431605997495353221893310546875

由于此近似值比 2.68 略微接近 2.67,因此将其舍入。

如果您担心十进制中位数要四舍五入,请考虑使用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 中不超过 1 个部分。对于大多数任务来说,这已经足够了,但是您需要记住,它不是十进制算术,并且每个 float 操作都可能遇到新的舍入错误。

尽管确实存在病理情况,但是如果将finally结果的显示四舍五入为期望的小数位数,那么对于大多数随意使用浮点算术的情况,finally都会看到期望的结果。要更好地控制浮点数的显示方式,请参见格式字符串语法中的str.format()方法的格式说明符。

14.1. 表示错误

本节详细说明“ 0.1”示例,并说明如何自己对此类情况进行精确分析。假定基本熟悉二进制浮点表示。

“表示错误”是指以下事实:某些(大多数,实际上)十进制小数不能完全表示为二进制(基数 2)小数。这是 Python(或 Perl,C,C,Java,Fortran 和其他许多语言)经常不显示您期望的确切十进制数字的主要原因:

>>> 0.1 + 0.2
0.30000000000000004

这是为什么? 1/10 和 2/10 不能精确表示为二进制分数。如今(2010 年 7 月),几乎所有机器都使用 IEEE-754 浮点算法,并且几乎所有平台都将 Python 浮点数 Map 到 IEEE-754“双精度”。 754 个双精度数包含 53 位精度,因此在 Importing 时,计算机努力将 0.1 转换为* J */2 ** * N 形式的最接近分数,其中 J *是正好包含 53 位的整数。Rewrite

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

as

J ~= 2**N / 10

并回想起* J 正好有 53 位(是>= 2**52< 2**53), N *的最佳值为 56:

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

也就是说,* N 的唯一值是正好有 53 位的 N 。那么, J *的最佳可能值就是四舍五入的商:

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

由于余数大于 10 的一半,因此pass四舍五入获得最佳近似值:

>>> q+1
7205759403792794

因此,在 754 倍精度中,最接近 1/10 的最佳近似值是 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'。