浮点数用来存储计算机中的小数,与现实世界中的十进制小数不同的是,浮点数通过二进制的形式来表示一个小数。在深入了解浮点数的实现之前,先来看几个 Python 浮点数计算有意思的例子:
1 |
0.1 == 0.10000000000000000000001 |
1 |
True |
1 |
0.1+0.1+0.1 == 0.3 |
1 |
False |
IEEE 浮点数表示法
这些看起来违反常识的“错误”并非 Python 的错,而是由浮点数的规则所决定的,即使放到其它语言中结果也是这样的。要理解计算机中浮点数的表示规则,先来看现实世界中十进制小数是如何表示的:
1 |
1.234 = 1 + 1/10 + 2/100 + 3/1000 |
可以用下面的公式来表示:
其中 是十进制中 0~9 的数字。而如果是一个二进制的小数:
1 |
1.001 = 1 + 0/2 + 0/4 + 1/8 |
可以用下面的公式来表示:
其中 是二进制中的 0 或 1。Python 中的浮点数都是双精度的,也就说采用 64 位来表示一个小数,那这 64 位分别有多少用来表示整数部分和小数部分呢?根据 IEEE 标准,考虑到符号位,双精度表示法是这样分配的:
也就是说用1位表示符号位,11位表示整数部分,52位表示小数部分。正如十进制中我们无法精确表示某些分数(如10/3
),浮点数中通过 d1/2 + d2/4 + ...
的方式也会出现这种情况,比如上面的例子中,十进制中简单的 0.1
就无法在二进制中精确描述,而只能通过近似表示法表示出来:
1 |
(0.1).as_integer_ratio() |
1 |
(3602879701896397, 36028797018963968) |
也就是说 0.1
是通过 3602879701896397/36028797018963968
来近似表示的,很明显这样近似的表示会导致许多差距很小的数字公用相同的近似表示数,例如:
1 |
(0.10000000000000001).as_integer_ratio() |
1 |
(3602879701896397, 36028797018963968) |
在 Python 中所有这些可以用相同的近似数表示的数字统一采用最短有效数字来表示:
1 |
print(0.10000000000000001) |
1 |
0.1 |
浮点数运算
既然有些浮点数是通过近似值表示的,那么在计算过程中就很容易出现误差,就像最开始的第二个例子一样:
1 2 3 4 5 |
a = .1 + .1 + .1 b = .3 print(a.as_integer_ratio()) print(b.as_integer_ratio()) print(a == b) |
1 2 3 |
(1351079888211149, 4503599627370496) (5404319552844595, 18014398509481984) False |
为了解决运算中的问题,IEEE 标准还指定了一个舍入规则(round),即 Python 中内置的 round
方法,我们可以通过舍入的方式取得两个数的近似值,来判断其近似值是否相等:
1 |
round(a, 10) == round(b, 10) |
1 |
True |
当然这种舍入的方式并不一定是可靠的,依赖于舍入的选择的位数,位数太大,就失去了 round
的作用,太小,就会引入别的错误:
1 2 |
print(round(a, 17) == round(b, 17)) print(round(0.1, 1) == round(0.111, 1)) |
1 2 |
False True |
Python 中使用更精确的浮点数可以通过 decimal
和 fractions
两个模块,从名字上也能猜到,decimal
表示完整的小数,而 fractions
通过分数的形式表示小数:
1 2 3 4 5 6 7 8 9 |
from decimal import Decimal a = Decimal(0.1) b = Decimal(0.1000000000000001) c = Decimal(0.10000000000000001) print(a) print(b) print(c) a == b == c |
1 2 3 4 5 6 7 8 9 |
0.1000000000000000055511151231257827021181583404541015625 0.10000000000000010269562977782697998918592929840087890625 0.1000000000000000055511151231257827021181583404541015625 False |