浮点数在计算机中表达为二进制(binary)小数。例如:十进制小数:
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 都不能精确表达为二进制小数。二进制来表达 1/10 是一个无限循环小数:
0.0001100110011001100110011001100110011001100110011...
在任何有限数量的位停下来,我们得到的都是近似值。今天在大多数机器上,浮点数的近似使用的小数以最高的 53 位为分子,2 的幂为分母。至于 1/10 这种情况,其二进制小数是
3602879701896397 / 2 ** 55
它非常接近但不完全等于1/10真实的值。
在许多数机器上,如果 Python 要打印 0.1 存储的二进制的真正近似值,将会显示:
0.1000000000000000055511151231257827021181583404541015625
这么多位的数字对大多数人是没有用的,所以 Python 显示一个舍入后的值:
0.1
只要记住即使打印的结果看上去是精确的 1/10,真正存储的值是最近似的二进制小数。
有趣地是,存在许多不同的十进制数共享着相同的近似二进制小数。例如,数字 0.1 和 0.10000000000000001 以及 0.1000000000000000055511151231257827021181583404541015625 都是 3602879701896397 / 2 ** 55 的近似值。因为所有这些十进制数共享相同的近似值,在保持恒等式 eval(repr(x)) == x 的同时,显示的可能是它们中的任何一个。
历史上,Python 提示符和内置的 repr() 函数选择一个 17 位精度的数字,0.10000000000000001。从 Python 3.1 开始,Python(在大多数系统上)能够从这些数字当中选择最短的一个并简单地显示 0.1。
注意,这是二进制浮点数的自然性质:它不是 Python 中的一个 bug,也不是开发者的代码中的 bug。用户会看到所有支持硬件浮点数算法的语言都会有这个现象(尽管有些语言默认情况下或者在所有输出模式下可能不会 显示 出差异)。
为了输出更好看,我们可以用字符串格式化来生成固定位数的有效数字:
认识到这一点很重要:开发者应当知道自己是在简单地舍入真实机器值的显示。
例如,既然 0.1 不是精确的 1/10,3 个 0.1 的值相加可能也不会得到精确的 0.3:
另外,既然 0.1 不能更接近 1/10 的精确值而且 0.3 不能更接近 3/10 的精确值,使用 round() 函数提前舍入也没有帮助:
虽然这些数字不可能再更接近它们想要的精确值,round() 函数可以用于在计算之后进行舍入,这样的话不精确的结果就可以和另外一个相比较了:
二进制浮点数计算有很多这样意想不到的结果。
最后我们要说,虽然对于不同场景下浮点数的处理“没有统一的答案”,但也不要过分惧怕浮点数。Python 浮点数计算中的误差源之于浮点数硬件,大多数机器上每次计算误差不超过 2**53 分之一。对于大多数任务这已经足够了,但是开发者要在心中记住这不是十进制算法,每个浮点数计算可能会带来一个新的舍入错误。
虽然确实有问题存在,对于大多数平常的浮点数运算,开发者只要简单地将最终显示的结果舍入到自己期望的十进制位数,就会得到自己期望的最终结果。str() 通常已经足够用了,对于更好的控制可以参阅 格式化字符串语法中 str.format() 方法的格式说明符。
对于需要精确十进制表示的情况,可以尝试使用 decimal 模块,它实现的十进制运算适合会计方面的应用和高精度要求的应用。
fractions 模块支持另外一种形式的运算,它实现的运算基于有理数(因此像1/3这样的数字可以精确地表示)。
如果开发者是浮点数操作的重度使用者,可以看一下由 SciPy 项目提供的 Numerical Python 包和其它用于数学和统计学的包。