前言
在文章开始之前先看下面“诡异”的一幕。
a, b = 0.1, 0.2 print(a + b == 0.3) print(a + b) out: False 0.30000000000000004 复制代码
0.1 + 0.2 == 0.3 结果竟然为 False
?不知道大家第一次见到这个场景作何感想,反正我是有点怀疑人生,为什么会产生这样的结果呢,下面详细看一下。
浮点数的限制
浮点数在计算机硬件中表示为一个以 2 为基数(二进制)的小数。我们先看看如果用十进制和二进制来表示0.125(10)。
十进制
0.125(10)
等于
1\times10^{-1} + 2\times10^{-2} + 5\times10^{-3} = \cfrac{1}{8}
>**二进制**<br> >0.001(2)<br> >$$ 0\times2^{-1} + 0\times2^{-2} + 1\times2^{-3} = \cfrac{1}{8}
这两个小数均表示 0.125(10),唯一真正的区别是第一个是以 10 为基数的小数表示法,第二个则是 2 为基数。
不幸的是,大多数的十进制小数都不能精确地表示为二进制小数,但有些浮点数也能够用二进制精确的表述,条件是位数有限且分母能表示成 2^n
的小数。如 0.5, 0.125。这将导致在大多数情况下,你输入的十进制浮点数都只能近似地以二进制浮点数形式储存在计算机中。
正如上文中的 0.1 ,我们手动计算一下它的二进制结果。
注:十进制整数转二进制方法:除2取余;十进制小数转二进制方法:乘2除整
计算过程:
0.1 * 2 = 0.2 # 0 0.2 * 2 = 0.4 # 0 0.4 * 2 = 0.8 # 0 0.8 * 2 = 1.6 # 1 0.6 * 2 = 1.2 # 1 0.2 * 2 = 0.4 # 0 0.4 * 2 = 0.8 # 0 ..... 复制代码
从上面结果可以看出,0.1 的二进制为:
0.0001100110011001100110011001100110011001100110011... 复制代码
这是一个二进制无限循环小数,但计算机内存有限,我们不能储存所有的小数位数。那如何解决呢?
答案就是从末尾某个位置截断,直接取近似值,因此,在目前大部分编程语言(支持处理器浮点运算)中,浮点数都只能近似地使用二进制小数表示。
很多人使用 Python
的时候都不会意识到这个差异的存在,因为 Python
只会输出计算机中存储的二进制值的十进制近似值。但我们要牢记,即使输出的结果看起来好像就是 0.1 的精确值,实际储存的值只是最接近 0.1 的计算机可表示的二进制值。
解决方式
1.decimal
decimal
模块可以进行十进制数学计算,我们将浮点数转成字符串进行运算。
from decimal import Decimal a, b = Decimal('0.1'), Decimal('0.2') a + b == Decimal('0.3') out:True 复制代码
2.numpy.float32
用 numpy
模块中的32为浮点型保存数据。
import numpy as np temp = np.array([0.1, 0.2, 0.3], dtype=np.float32) temp[0] + temp[1] == temp[2] 复制代码
当然体高精度的同时,性能可能会降低,在实际应用中这些近似值造成的细微偏差可能不会造成什么影响。如果碰到了留个心眼就好!
说了这么多,总结出一句话就是:浮点数转二进制时丢失了精度,计算完再转回十进制时和理论结果不同。不知道大家get到了吗?