14. 浮点运算:问题和限制 — Python 文档

来自菜鸟教程
Python/docs/2.7/tutorial/floatingpoint
跳转至:导航、​搜索

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 的数字,十进制值 0.1 都不能完全表示为基数为 2 的分数。 在基数 2 中,1/10 是无限重复的分数

0.0001100110011001100110011001100110011001100110011...

停在任何有限数量的位上,您就会得到一个近似值。

在运行 Python 的典型机器上,Python 浮点数有 53 位精度可用,因此当您输入十进制数 0.1 时,内部存储的值是二进制小数

0.00011001100110011001100110011001100110011001100110011010

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

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

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

这比大多数人认为有用的位数要多,因此 Python 通过显示舍入值来保持可管理的位数

>>> 0.1
0.1

重要的是要意识到,这实际上是一种错觉:机器中的值不完全是 1/10,您只是将真实机器值的 显示 取整。 一旦您尝试使用这些值进行算术运算,这个事实就会变得显而易见

>>> 0.1 + 0.2
0.30000000000000004

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

其他惊喜随之而来。 例如,如果您尝试将值 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 中的 1 部分。 这对于大多数任务来说已经足够了,但是您确实需要记住它不是十进制算术,并且每个浮点运算都可能遭受新的舍入误差。

虽然病理情况确实存在,但对于浮点运算的大多数随意使用,如果您只是将最终结果的显示四舍五入为您期望的小数位数,您最终会看到您期望的结果。 要精确控制浮点数的显示方式,请参阅 格式字符串语法 中的 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 浮点数映射到 IEEE-754 “双精度”。 754 个双精度包含 53 位精度,因此在输入时,计算机力求将 0.1 转换为最接近的分数,其形式为 J/2**N 其中 J[ X169X] 是一个正好包含 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 的最佳近似值是超过 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”。