计算机在处理浮点数时会用二进制表示,遇到无法用二进制精确表示的十进制浮点数时便会根据精确度位数进行截断,Python 也不例外。
Python 精度
python 默认使用的是 double 精度, 浮点数在计算机中都是以二进制保存,当有无法精确表示的二进制数字时便会产生截断, 这就导致了在有限精度下,电脑为自己把精度范围外的小数“掐掉”,导致结果不准确。
可以随时在 Python 环境下测试:
代码语言:javascript复制0.1 0.2
-->
0.30000000000000004
也就是说,如果你使用很精确的浮点数字计算的结果作为一个逻辑表达式时,可能会发生问题:
代码语言:javascript复制0.1 0.2 == 0.3
-->
False
问题原理
double 用64 个bit 位表示数据
有效精度位数是 52 位,那么当表示的小数用52bit 无法精确表示时便会截断
示例代码:
代码语言:javascript复制import numpy as np
import struct
def binary_add(a, b, pre_num=64):
int_a = np.floor(a).astype('int64')
int_b = np.floor(b).astype('int64')
int_sum = int_a int_b
double_a = ""
double_b = ""
r_a = a - int_a
r_b = b - int_b
temp_a = r_a
temp_b = r_b
def up(num):
t = num * 2
if t >= 1:
x = 1
t -= 1
else:
x = 0
return t, x
for index in range(pre_num):
temp_a, bin_a = up(temp_a)
temp_b, bin_b = up(temp_b)
double_a = double_a str(bin_a)
double_b = double_b str(bin_b)
print(double_a)
print(double_b)
sum2 = int(double_a, 2) int(double_b, 2)
sum_bin = bin(sum2)[2:]
print(sum_bin)
res_p = sum2 / 2 ** 64
return res_p int_sum
res = binary_add(0.1, 0.2)
print(res)
pass
-->
0001100110011001100110011001100110011001100110011001101000000000
0011001100110011001100110011001100110011001100110011010000000000
0100110011001100110011001100110011001100110011001100111000000000
0.30000000000000004
- 输出信息第一行为 0.1 的小数部分二进制表示,可以说:
事实上 0.1 的二进制表示是一个以 1100 为循环体的无限循环小数,到有效位 53 位时被截断,之后的数据变 为了全零
- 同理,第二行有:
本质上就是 0.1 左移一位而已,也是 1100 的无限循环小数,在第 53 位被截断
- 二者变成整数相加后得到 :
- 该数据除以 2^{64} 得到 0.1 0.2 的结果,就是 0.30000000000000004
以上流程基本就是 Python 内部计算 0.1 0.2 时的过程,其余语言也一样,这是由无限循环小数难以精确表示导致的。
解决方案
如果有需要更高精度计算的需求,可以继续提升有效 bit 位数。
如果仍然无法达到精度要求,可以使用 Python decimal 包实现
参考资料
- https://zhuanlan.zhihu.com/p/572700804
文章链接: https://cloud.tencent.com/developer/article/2277072