# 浮点数 · 0.1 + 0.2 为什么不等于 0.3

> 计算机用浮点数近似实数——本质是二进制的科学计数法。这一章讲清 IEEE 754 的结构、为什么 0.1 根本存不准、由此引出的第一条铁律：浮点数永远别用 == 比较，要用容差。

- 作者：David（道雾轩）
- 专栏：数值计算的艺术（https://daiw.org/manual/numerical-computing.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/numerical-computing/floating-point
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 浮点数 · 0.1 + 0.2 为什么不等于 0.3

序里那行 `0.1 + 0.2 == False` 是整个专栏的引子。要理解它，得先看清：**计算机到底怎么存一个实数。**

## 浮点数：二进制的科学计数法

实数有无穷多个，而计算机内存有限——不可能精确存下每一个。于是有了**浮点数（floating-point）**：一种用**固定位数**近似表示实数的方案，本质是**二进制的科学计数法**。

十进制科学计数法把数写成 $\pm d.ddd \times 10^{e}$；浮点数把它搬到二进制：

$$
x = (-1)^{s} \times 1.f \times 2^{e}
$$

- $s$：**符号位**（正负）。
- $f$：**尾数（mantissa）**，决定**精度**（有几位有效数字）。
- $e$：**指数**，决定**范围**（能表示多大多小）。

最常用的 **`float64`（双精度）** 用 64 位：1 位符号 + 11 位指数 + 52 位尾数。`float32`（单精度）则是 1 + 8 + 23。位数是**固定**的，这就是一切误差的根源——**有限的位，装不下无限的实数，只能四舍五入到最接近的那个能表示的值。**

## 0.1 存不准

问题来了：**很多十进制里「整齐」的小数，在二进制里是无限循环的。** 就像 $\frac{1}{3}=0.333\dots$ 在十进制里写不完，$0.1$ 在**二进制**里也是无限循环：$0.000110011001100\dots$。而尾数只有 52 位，只能截断、四舍五入。所以计算机里存的 `0.1`，其实**不是** $0.1$，而是最接近它的那个浮点数。用 `Decimal` 把真实存进去的值揪出来看：

```python
from decimal import Decimal
print(Decimal(0.1))
# 0.1000000000000000055511151231257827021181583404541015625
```

看到了吗？`0.1` 存进去，实际是 `0.10000...0555...`——多出一点点尾巴。`0.2`、`0.3` 也各有各的尾巴。于是：

```python
>>> 0.1 + 0.2
0.30000000000000004      # 两个近似值相加,尾巴凑到了小数点后 17 位
>>> 0.3
0.29999999999999998889...  # 而 0.3 自己的近似,又是另一个值
```

`0.1` 的近似 + `0.2` 的近似，**不等于** `0.3` 的近似——差了大约 $10^{-17}$。`==` 于是返回 `False`。**这不是谁的 bug，是有限精度的必然。**

## 第一条铁律：浮点数别用 == 比

<Callout type="warn">
  **浮点数几乎永远不要用 `==` 判断相等——要用「容差」。** 因为任何一串浮点运算都会累积微小的舍入误差，两个「数学上相等」的量，算出来往往差那么一丁点。正确姿势是判断它们**足够接近**：

  ```python
  # ✗ 危险
  if a == b: ...

  # ✓ 正确:差的绝对值小于一个小容差
  if abs(a - b) < 1e-9: ...

  # ✓ 更稳:NumPy 提供了带相对+绝对容差的现成函数
  import numpy as np
  np.isclose(0.1 + 0.2, 0.3)      # True
  ```

  容差取多大？看你的量级和精度需求（下一章的[机器精度](https://daiw.org/manual/numerical-computing/machine-epsilon)给你标尺）。但「用容差、不用 `==`」这条，是每个碰浮点数的程序员都该刻进肌肉的第一反应。
</Callout>

## 顺带认识：inf 和 nan

浮点数还有几个「特殊值」，迟早会遇到：

```python
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      # nan
```

- **`inf`**（无穷）：溢出或除以 0。
- **`nan`**（非数）：$0/0$、$\infty-\infty$ 这类没意义的运算。

<Callout type="info">
  **`nan` 有个坑：它和任何值比较都返回 `False`，包括它自己！** `np.nan == np.nan` 是 `False`。所以判断一个值是不是 `nan`，不能用 `== np.nan`，要用 `np.isnan(x)`。`nan` 还会「传染」——任何和 `nan` 的运算结果都是 `nan`，所以一个中途冒出的 `nan` 会悄悄污染整个计算，最后你只看到满屏 `nan` 却不知源头在哪。这是数值程序常见的调试噩梦。
</Callout>

## 小结与下一章

计算机用**有限位数的浮点数**近似实数（二进制科学计数法：符号 + 尾数 + 指数）。位数有限，导致像 `0.1` 这种十进制整数在二进制里存不准，`0.1+0.2≠0.3` 由此而来。第一条铁律：**浮点数用容差比较，别用 `==`**；顺带认识会传染的 `nan`。

「存不准」到底能有多不准？这个「一丁点」误差的大小，是有精确标尺的——它叫**机器精度**。下一章我们量化它，并看看这些微小误差在长计算里怎么累积、又怎么用巧法子压住。👉 [机器精度：误差有多大，以及怎么压住它](https://daiw.org/manual/numerical-computing/machine-epsilon)。

## 参考文献

- [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 —— 通俗入门。
