浮点数 · 0.1 + 0.2 为什么不等于 0.3
计算机用浮点数近似实数——本质是二进制的科学计数法。这一章讲清 IEEE 754 的结构、为什么 0.1 根本存不准、由此引出的第一条铁律:浮点数永远别用 == 比较,要用容差。
浮点数 · 0.1 + 0.2 为什么不等于 0.3
序里那行 0.1 + 0.2 == False 是整个专栏的引子。要理解它,得先看清:计算机到底怎么存一个实数。
浮点数:二进制的科学计数法
实数有无穷多个,而计算机内存有限——不可能精确存下每一个。于是有了浮点数(floating-point):一种用固定位数近似表示实数的方案,本质是二进制的科学计数法。
十进制科学计数法把数写成 ;浮点数把它搬到二进制:
- :符号位(正负)。
- :尾数(mantissa),决定精度(有几位有效数字)。
- :指数,决定范围(能表示多大多小)。
最常用的 float64(双精度) 用 64 位:1 位符号 + 11 位指数 + 52 位尾数。float32(单精度)则是 1 + 8 + 23。位数是固定的,这就是一切误差的根源——有限的位,装不下无限的实数,只能四舍五入到最接近的那个能表示的值。
0.1 存不准
问题来了:很多十进制里「整齐」的小数,在二进制里是无限循环的。 就像 在十进制里写不完, 在二进制里也是无限循环:。而尾数只有 52 位,只能截断、四舍五入。所以计算机里存的 0.1,其实不是 ,而是最接近它的那个浮点数。用 Decimal 把真实存进去的值揪出来看:
from decimal import Decimal
print(Decimal(0.1))
# 0.1000000000000000055511151231257827021181583404541015625看到了吗?0.1 存进去,实际是 0.10000...0555...——多出一点点尾巴。0.2、0.3 也各有各的尾巴。于是:
>>> 0.1 + 0.2
0.30000000000000004 # 两个近似值相加,尾巴凑到了小数点后 17 位
>>> 0.3
0.29999999999999998889... # 而 0.3 自己的近似,又是另一个值0.1 的近似 + 0.2 的近似,不等于 0.3 的近似——差了大约 。== 于是返回 False。这不是谁的 bug,是有限精度的必然。
第一条铁律:浮点数别用 == 比
浮点数几乎永远不要用 == 判断相等——要用「容差」。 因为任何一串浮点运算都会累积微小的舍入误差,两个「数学上相等」的量,算出来往往差那么一丁点。正确姿势是判断它们足够接近:
# ✗ 危险
if a == b: ...
# ✓ 正确:差的绝对值小于一个小容差
if abs(a - b) < 1e-9: ...
# ✓ 更稳:NumPy 提供了带相对+绝对容差的现成函数
import numpy as np
np.isclose(0.1 + 0.2, 0.3) # True容差取多大?看你的量级和精度需求(下一章的机器精度给你标尺)。但「用容差、不用 ==」这条,是每个碰浮点数的程序员都该刻进肌肉的第一反应。
顺带认识:inf 和 nan
浮点数还有几个「特殊值」,迟早会遇到:
1.0 / 0.0 # 报错 ZeroDivisionError(Python 纯 float)
np.float64(1) / 0 # inf(NumPy:正无穷,还会 warning)
np.float64(0) / 0 # nan(Not a Number,0/0 这种没定义的结果)
np.inf - np.inf # naninf(无穷):溢出或除以 0。nan(非数):、 这类没意义的运算。
nan 有个坑:它和任何值比较都返回 False,包括它自己! np.nan == np.nan 是 False。所以判断一个值是不是 nan,不能用 == np.nan,要用 np.isnan(x)。nan 还会「传染」——任何和 nan 的运算结果都是 nan,所以一个中途冒出的 nan 会悄悄污染整个计算,最后你只看到满屏 nan 却不知源头在哪。这是数值程序常见的调试噩梦。
小结与下一章
计算机用有限位数的浮点数近似实数(二进制科学计数法:符号 + 尾数 + 指数)。位数有限,导致像 0.1 这种十进制整数在二进制里存不准,0.1+0.2≠0.3 由此而来。第一条铁律:浮点数用容差比较,别用 ==;顺带认识会传染的 nan。
「存不准」到底能有多不准?这个「一丁点」误差的大小,是有精确标尺的——它叫机器精度。下一章我们量化它,并看看这些微小误差在长计算里怎么累积、又怎么用巧法子压住。👉 机器精度:误差有多大,以及怎么压住它。
参考文献
- [1] David Goldberg.「What Every Computer Scientist Should Know About Floating-Point Arithmetic.」1991. —— 浮点数的权威长文。
- [2] IEEE 754-2019.「Standard for Floating-Point Arithmetic.」—— 浮点数标准本身。
- [3] Python 官方文档.「Floating Point Arithmetic: Issues and Limitations.」https://docs.python.org/3/tutorial/floatingpoint.html —— 通俗入门。