.. include:: ../LINKS.rst .. _tut-fp-issues: ************************************************************************* 浮点算术: 问题和限制 ************************************************************************* .. sectionauthor:: Tim Peters 浮点数在计算机中以二进制的除法表示. 比如, 十进制的:: 0.125 其值为 1/10 + 2/100 + 5/1000, 同样, 以二进制表示则为:: 0.001 其值为 0/2 + 0/4 + 1/8. 这两种表示法的值是一样的, 唯一的区别是, 前者以十进制表示, 而后者则以二进制表示. 不幸的是, 大多数的十进制小数都无法严格的以二进制来表示. 一个结果就是, 普遍来说, 你输入的十进制的小数, 通常只是以接近的二进制数表示. 在十进制中这个问题很容易理解. 考虑分数 1/3 . 你可以用一个接近的十进制数表示:: 0.3 要更一些, :: 0.33 更好一些, :: 0.333 等等. 但是不管你怎么写, 都不是严格的等于 1/3, 但是可以使结果更接近于 1/3. 同样, 无论你用了多少位, 二进制的数也无法精确表示十进制的 0.1 . 在以二为底的情况下, 将是个无限循环 :: 0.0001100110011001100110011001100110011001100110011... 在任意有限的位上停止, 可以得到一个近似值. 在今天的大多数机子上, 浮点数近似地使用二元的分数来表示, 其分子是一个 53 位的数字, 分母则是 2 的幂. 像 1/10, 其二进制表示为 ``3602879701896397 / 2 ** 55``. 这个很接近, 但不是准确的等于. 很多用户因为这些值显示的方法而并不知道近似. Python 仅仅打印一个合适的十进制分数, 而真正的二进制近似还是存储于机器中. 在大多数情况下, 如果让 Python 打印一个十进制小数, 那么其会以真实存储的数字显示, 例如 0.1 :: >>> 0.1 0.1000000000000000055511151231257827021181583404541015625 此处的位数比一般用到的要多, 所以 Python 使用一个近似的值代替之:: >>> 1 / 10 0.1 只要记住, 尽管打印的值看起来是准确的 1/10 , 但是真正存储的只是最接近的二元分数罢了. 有趣的是, 有很多不同的值共享同样的分数. 举个例子, 数字 ``0.1`` 和 ``0.10000000000000001`` 及 ``0.1000000000000000055511151231257827021181583404541015625`` 都是 ``3602879701896397 / 2 ** 55`` 的近似. 因为同样的分数值表示不同值的近似, 这样任何的值都可以保证不变式 ``eval(repr(x)) == x``. 历史原因, Python 的提示符和内置的 :func:`repr` 函数会选择 17 位, ``0.10000000000000001`` . 在 Python 3.1 开始, Python (在绝大数的系统上), 会选择最简的表示 ``0.1``. 注意, 二进制表示的浮点数在此处是非常自然的: 这个不是 Python 中的 bug, 也不是你代码中的 bug . 在很多支持硬件浮点型的语言中也可以看到这样的事情 (尽管有些语言默认下不会显示不同, 或在所有的输出模式下). 对于更多友好的输出, 你可能需要使用字符串格式化来产生一个有限制的数字:: >>> format(math.pi, '.12g') # give 12 significant digits '3.14159265359' >>> format(math.pi, '.2f') # give 2 digits after the point '3.14' >>> repr(math.pi) '3.141592653589793' 有一点很重要, 你需要意识到, 在真实情况下, 这是个幻觉: 你仅仅是四舍五入了显示的真实值. 其中一个幻觉会产生另一个. 举个例子, 因为 0.1 并不是严格的 1/10, 三个 0.1 相加并不会生成准确的 0.3:: >>> .1 + .1 + .1 == .3 False 同样, 因为 0.1 不能够得到更接近 1/10 的值, 而 0.3 不能得到更接近 3/10 的值, 因此使用 :func:`round` 函数来进行四舍五入也是不起作用的:: >>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1) False 尽管数字不能够更接近它们理想上的准确值, :func:`round` 函数在计算后使用, 确实可以实现两个数字之间的比较:: >>> round(.1 + .1 + .1, 10) == round(.3, 10) True 像这样的浮点数算法, 会导致很多令人奇怪的事情. "0.1" 的问题将在后面更详细的解释, 具体看 "Representation Error" 那节. 更多常见的令人吃惊的事情, 可以参考 `The Perils of Floating Point `_ . 就像后面说的, "没有简单的答案." 但是, 对于浮点数请不要过度谨慎. 在 Python 中这些浮点数的问题来源于其硬件, 在多数的机器中, 浮点的精度没有必要达到 1/2\*\*53 . 对于普通的任务已经足够了, 你需要记住的就是, 这不是算数的问题, 每个浮点数操作都会遇到这样的问题. 当不合理的情况真的存在时, 对于多数情况, 你最终还是能得到希望的结果, 如果你将显示的数值进行四舍五入, 并确保你所需要的位数. :func:`str` 函数经常就能满足需要了, 使用 :meth:`str.format` 方法, 来指明其 :ref:`formatstrings`. 在需要严格的数值表示时, 试试使用 :mod:`decimal` 模块, 这个模块实现了用于账目运算或更高精度时用到的数值算法. 另一种就是 :mod:`fractions` 模块, 它实现了基于有理数的算法 ( 所以 1/3 就可以准确的表述 ). 如果你有大量浮点数的运算, 那么你可以看看 Python 的数值库或其他的计算和统计的包, SciPy 这个项目对此有很好的支持. 参考 . Python 提供了工具来帮助你获得浮点数的准确值. 你可以使用 :meth:`float.as_integer_ratio` 方法来表示一个分数:: >>> x = 3.14159 >>> x.as_integer_ratio() (3537115888337719, 1125899906842624) 因为这个比率是准确的, 它就可以用来比较原始的数字:: >>> x == 3537115888337719 / 1125899906842624 True :meth:`float.hex` 方法以十六进制表述, 这也同样给出了一个被你计算机准确存储的值:: >>> x.hex() '0x1.921f9f01b866ep+1' 前面的十六进制表示, 可以用来重新建立一个浮点值:: >>> x == float.fromhex('0x1.921f9f01b866ep+1') True 因为这个表示是严格的, 所以对于不同版本的 Python (跨平台) 都是兼容的, 而且也可以和其他的语言进行交换 (比如 java 和 C99). 另一个有用的工具就是 :func:`math.fsum` 函数. 它可以在计算总和时减少精度的丢失. 它会记录在求和时丢失的精度. 这样误差就不会积累而最终影响结果了. >>> sum([0.1] * 10) == 1.0 False >>> math.fsum([0.1] * 10) == 1.0 True .. _tut-fp-error: 表示错误 ==================== 本节会更详细的解释 "0.1" 的例子, 并且教你如何进行准确的分析. 此处假设你已有了基本的二元浮点数表示的基础. :dfn:`Representation error` 涉及到这样的事实, 有些 (更准确来书是大多数) 小数的分数表示不能够以二进制为底的分数表述. 这就是主要的原因, 为何 Python (或者 Perl, C, C++, Java, Fortran, 还有更多的) 常常不能够如你所愿的表示. 为什么会那样? 1/10 不能够被二进制的分数准确表示. 基本上全部的机器在今 (2000年11月) 来说都是使用了 IEEE-754 浮点数算法, 并且几乎全部的平台将 Python 的浮点映射为 IEEE-754 "double精度". 754 doubles 包含了 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 <= 2**56 // 10 < 2**53 True 也就是说, 56 是唯一能让 *J* 为 53 位的 *N* 值. 而 *J* 最有可能的值就是那个商:: >>> q, r = divmod(2**56, 10) >>> r 6 因为剩余的如果大于10的一半, 那么最好的近似就是进一位:: >>> q+1 7205759403792794 所以以 754 double 精度表示的 1/10 最合适的值就是:: 7205759403792794 / 2 ** 56 将分子分母约化:: 3602879701896397 / 2 ** 55 注意, 因为我们进了一位, 所以值会比 1/10 稍微大一点; 如果我们没有进位, 那么商又会比 1/10 稍微小点. 但无论如何, 都不是准确的 1/10! 所以计算机从没有看过 1/10: 它看到的是前面给出的分数, 以 754 双进度近似的结果:: >>> 0.1 * 2 ** 55 3602879701896397.0 如果我们将这个分数乘以 10\*\*55, 我们可以看到55位的数字:: >>> 3602879701896397 * 10 ** 55 // 2 ** 55 1000000000000000055511151231257827021181583404541015625 这意味存于计算机中的准确值等于 0.1000000000000000055511151231257827021181583404541015625. 很多语言 (包括 Python 的旧版本) 不是直接显示所有的位数, 而是将其保留为17位有效数字:: >>> format(0.1, '.17f') '0.10000000000000001' :mod:`fractions` 和 :mod:`decimal` 模块使这些计算变得简单:: >>> from decimal import Decimal >>> from fractions import Fraction >>> Fraction.from_float(0.1) Fraction(3602879701896397, 36028797018963968) >>> (0.1).as_integer_ratio() (3602879701896397, 36028797018963968) >>> Decimal.from_float(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') >>> format(Decimal.from_float(0.1), '.17') '0.10000000000000001' .. seealso:: (^.^) - 原文: http://docs.python.org/py3k/tutorial/floatingpoint.html - 初译: `刘鑫`_ - 精译: `DocsPy3zh`_ - 校对: `Zoom.Quiet`_ - 复审: