浮点数在计算机中表达为二进制(binary)小数。例如:十进制小数
0.125
是 1/10 + 2/100 + 5/1000 的值,同样二进制小数:
0.001
是 0/2 + 0/4 + 1/8。这两个数值相同。唯一的实质区别是第一个写为十进制小数记法,第二个是二进制。
遗憾的是,大多数十进制小数不能精确的表达二进制小数。因此,总的来说,我们输入的十进制浮点数的实际存储在机器上是近似二进制浮点数。
这个问题更早的时候首先在十进制中发现。考虑小数形式的 1/3 ,你可以来个十进制的近似值。
0.3
或者更进一步的,
0.33
诸如此类。如果你写多少位,这个结果永远不是精确的 1/3 ,但是可以无限接近 1/3 。
同样,无论在二进制中写多少位,十进制数 0.1 都不能精确表达为二进制小数。二进制来表达 1/10 是一个无限循环小数:
0.0001100110011001100110011001100110011001100110011...
在任意无限位数值中中止,你可以得到一个近似。在今天的大多数机器上 Python ,一共有 53 位的精度来表示一个浮点数,所以当你输入十进制的 0.1 的时候,看到是一个二进制的小数.在1 / 10的情况下,二进制分数为 3602879701896397 / 2 * 55,接近但不等于1 / 10的真正价值。
许多用户都没有意识到近似由于显示值的方法。Python 只打印十进制小数以二进制存储在 机器中的近似值的十进制近似表示。 在大多数机器上,如果 Python 打印 0.1 的二进制存储的真正十进制值,应该显示为这样
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
这比大多数人找到有用的更数字化,所以 Python 保持数字可控的数量显示一个圆形的值来代替
>>> 1 / 10
0.1
记住,即使打印结果看起来像 1 / 10 的精确值,但是实际存储的值是最近表示二进制的分数。
有趣的是,有许多不同的小数,共享相同的最近近似的二进制小数。在数字 0.1 和 0.1000000000000000,0.1000000000000000055511151231257827021181583404541015625 都由 3602879701896397 / 2 * 55
近似。由于所有这些十进制值共享相同的近似。他们中的任何一个可以同时仍然保持不变的 eval 显示(repr(x))= = x
。
历史上,Python 提示符和内置的 repr() 功能会选择一个17位数,0.1000000000000000。从 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'
认识到这个幻觉的真相很重要:机器不能精确表达 1/10,你可以简单的截断 显示 真正的机器值。 一种幻觉可能招致另一个。例如,因为0.1是不完全的1 / 10,0.1和三的值也不能精确0.3,
>>> .1 + .1 + .1 == .3
False
同时,由于 0.1 不能接近 1 / 10 的准确值和 0.3 不能接近 53/ 10 的精确值,然后用 round() 功能不能帮助预舍入:
>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)
False
虽然数字不能接近其预期的精确值,但是 round() 函数对后四舍五入是有用的,以至于结果与精确值舍入成为相互比较:
>>> round(.1 + .1 + .1, 10) == round(.3, 10)
True
浮点数据算法产生了很多诸如此类的惊奇。在“表现错误”一节中,这个 “0.1”问题详细表达了精度问问题。
最后我要说,“没有简单的答案”。还是不要过度的敌视浮点数!Python 浮点 数操作的错误来自于浮点数硬件,大多数机器上同类的问题每次计算误差不超过 2**53 分之一。对于大多数任务这已经足够让人满意了。但是你要在心中记住这不是十 进制算法,每个浮点数计算可能会带来一个新的精度错误。
问题已经存在了,对于大多数偶发的浮点数错误,你应该比对你期待的最终显示结果是否符合你的期待。 str() 通常够用了,完全的控制参见字符串语法中 str.format() 方法的格式化方式。
用例需要精确十进制表示,试着用它实现十进制运算适合会计应用高精度应用十进制模块。
以有理数为基础的执行算术分模块支持另一种准确算术。(所以像1/3那样的数字能更准确的表达)
如果你过渡使用浮点运算,你应该看一看数值 Python 包。很多数学和统计的操作包被SciPy项目提供。 Python 提供了工具,可以帮助在那些罕见的情况诸如当你真的想知道一个浮动的精确值的时候。浮动。 as_integer_ratio() 方法表达一个浮动的价值作为一个分数:
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
自比是精确的,它可用于无损重建原始值:
>>> x == 3537115888337719 / 1125899906842624
True
浮动。hex() 方法表示十六进制浮点数(16),通过你的计算机再次给予确切的值存储:
>>> x.hex()
'0x1.921f9f01b866ep+1'
这种精确的十六进制表示法可以用来精确重建的浮点值:
>>> x == float.fromhex('0x1.921f9f01b866ep+1')
True
由于精确的表示是准确的,可靠的移植值在不同版本的 Python 是有用的(平台独立性)。与其他语言支持相同的数据交换格式(如 Java 和 C99 中)。 另一个有用的工具是 math.fsum() 函数,它有助于减轻损失精度的总和。它跟踪的“失去的数字”的值添加到运行总和。那样可以使整体精度,误差不积累,从而影响最终的总角度的差异:
>>> sum([0.1] * 10) == 1.0
False
>>> math.fsum([0.1] * 10) == 1.0
True
表达错误
这一节详细说明“0.1”示例,教你怎样自己去精确的分析此类案例。假设这里 你已经对浮点数表示有基本的了解。
Representation error 提及事实上有些(实际是大多数)十进制小数不 能精确的表示为二进制小数。这是 Python (或 Perl,C,C++,Java,Fortran 以及其它很多)语言往往不能按你期待的样子显示十进制数值的根本原因。
这是为什么? 1/10 不能精确的表示为二进制小数。大多数今天的机器(2000年十一月)使用 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 <= 2**56 // 10 < 2**53
True
因此,56 是保持 J 精度的唯一 N 值。 J 最好的近似值是整除的商:
>>> q, r = divmod(2**56, 10)
>>> r
6
因为余数大于 10 的一半,最好的近似是取上界:
>>> q+1
7205759403792794
因此在 754 双精度中 1/10 最好的近似值是是 2**56 ,或:
7205759403792794 / 2 ** 56
分裂分子和分母的减少率:
3602879701896397 / 2 ** 55
要注意因为我们向上舍入,它其实比 1/10 稍大一点点。如果我们没有向上舍入,它会比 1/10 稍小一点。但是没办法让它 恰好 是 1/10 !