Python 浮点数陷阱详解:如何完美解决 x * 0.1 的精度问题

11 阅读1分钟

1. 现象: 为什么读到的温度计数据小数位有那么多?

刚到了一个温湿度传感器,我就把他接入到八哥开关小帮手上面。 数据是通过Modbus-RTU的协议传输到服务器上,是一个整型 int16AB 编码,刚好是10倍的关系, 计算公式为 value = 0.1 * read_value。 计算出来的数据很奇怪,18.9000000000002,后面多出来了好多的小数点,用眼睛看就知道有问题了。 我们希望的是得到 18.9,但计算机给出了一个带有微小“尾巴”的数字。这并不是 Python 的 bug,而是计算机底层存储浮点数方式导致的物理限制。

2. 原理:二进制的世界没有 0.1

计算机使用 IEEE 754 标准 的二进制浮点数来存储小数。

在十进制中,13\frac{1}{3} 是无限循环小数 (0.3333...0.3333...);同理,在二进制中,110\frac{1}{10} (即 0.10.1) 也是一个无限循环小数:

0.1(10)=0.00011001100110011...(2)0.1_{(10)} = 0.00011001100110011..._{(2)}

因为计算机的存储空间(53位有效数字)是有限的,它必须在某一位进行截断。这种截断导致了微小的精度丢失,就像把 0.3333...0.3333... 截断为 0.33330.3333 一样,结果虽然极其接近,但在数学上并不完全相等。

3. 采用 decimal 金融级精确计算来解决

Decimal 模块模拟了人类手算的方式,避免了二进制转换误差,这样我们算出来的数据就不会出现那么多的小数位了。

Python

from decimal import Decimal

# 核心原则:必须以 '字符串' 形式初始化
a = Decimal('189')
b = Decimal('0.1')

print(a * b)  
# 输出: 18.9 (类型为 Decimal)

警告:如果写成 Decimal(0.1),由于括号内的 0.1 已经是浮点数,误差会被带入 Decimal 中,导致前功尽弃。

4. 终于没有那么多的小数位了

经过 Decimal 的处理,终于可以计算出正确的数字了,没有那么多的小数位。