0.1 + 0.2 为什么不等于 0.3:IEEE 754 到底存了什么
先看现象
在大多数语言里,这一行都不会如你所愿:
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
JavaScript 一样:
> 0.1 + 0.2
0.30000000000000004
> 0.1 + 0.2 === 0.3
false
Java、C、Go、Ruby……只要用的是 IEEE 754 的双精度浮点,结果都一样。
这不是 bug,是这套表示法的必然结果。
为什么 0.1 存不准
十进制的小数位是「十分之一、百分之一、千分之一」,二进制的小数位是「二分之一、四分之一、八分之一」。
问题在于:有些十进制小数,用二进制根本写不出来。
0.5= 1/2 ✅ 写得出来0.25= 1/4 ✅0.75= 3/4 ✅0.1= 1/10 ❌ 写不出来
因为 10 = 2 × 5,分母里带着一个 5,而二进制只能用 2 的幂。所以 0.1 在二进制里是:
0.0001100110011001100110011001100110011...
无限循环,就像十进制里 1/3 = 0.3333... 一样。
那它实际存的是什么
双精度浮点一共 64 位,切三块:
部分 | 位数 | 作用
符号位 | 1 位 | 正负
指数 | 11 位 | 数量级(能表示约 10^-308 到 10^308)
尾数 | 52 位 | 有效数字,精度就在这里
52 位尾数,能提供约 15~17 位十进制有效数字。
0.1 的无限循环二进制被截断到 52 位,存下来的其实是:
0.1000000000000000055511151231257827021181583404541015625
比 0.1 稍微大一点点。0.2 同理,也比 0.2 稍微大一点点。
两个「稍微大一点」相加,误差累加,结果就偏到了 0.30000000000000004。
这个误差有多大,会不会越滚越大
单次误差在 10^-17 量级,看起来微不足道。但它有两个要命的性质:
① 相等判断会失效
0.1 + 0.2 == 0.3 # False
两个数学上相等的值,用 == 比会不相等。这是最容易踩的坑。
② 累加会漂
total = 0
for _ in range(10_000_000):
total += 0.1
print(total)
# 1000000.0000000188 左右,不是 1000000
一千万次累加,误差到了小数点后第 10 位。如果这是钱,就是实实在在的差账。
该怎么绕开
① 金额用整数存最小单位
不要用「元」存钱,用「分」:
price_cents = 1999 # 19.99 元
total = price_cents * 3 # 5997 分 = 59.97 元
整数运算没有精度问题,只在最后展示时除以 100。
② 需要精确十进制就用 Decimal
Python 的 decimal、Java 的 BigDecimal、C# 的 decimal,都是十进制表示,代价是慢一些:
from decimal import Decimal
Decimal('0.1') + Decimal('0.2') == Decimal('0.3') # True
注意:要用字符串构造,Decimal(0.1) 会先把 0.1 变成浮点再转,误差照样进来。
③ 浮点数比较要带容差
abs(a - b) < 1e-9 # 而不是 a == b
Python 标准库给了 math.isclose(),很多语言也有对应的函数。
一个反直觉的事实
误差不只来自 0.1 这种「循环小数」,也来自大数加法:
>>> 1e16 + 1 == 1e16
True
因为 1e16 需要的位数已经超出 52 位尾数能精确表示的范围,加 1 直接被舍掉了。
所以浮点数的精度是相对的,不是绝对的:数值越大,能精确到的小数位越少。
落一句
0.1 + 0.2 != 0.3 不是哪个语言没做好,是用有限位二进制表示无限位十进制小数的数学后果。
只要记住一条:要精确的场合(钱、计数、ID)别用浮点,浮点只用来算测量值和科学计算。
- 点赞
- 收藏
- 关注作者
评论(0)