<!-- title: 0.1+0.2为何不等于0.3 -->
<!-- domain: programming -->

# 0.1 + 0.2 ≠ 0.3：一条计算机科学的经典神课深度解读

## 概述

在所有让程序员"世界观崩塌"的瞬间里，`0.1 + 0.2 === 0.30000000000000004` 大概率排名第一。它在 Stack Overflow 上是史上访问量最大的问题之一，几乎每个开发者在职业生涯早期都会撞上它，然后大部分人选择耸耸肩绕过去——知道现象，却不明白机理。本期我们深度解读 Computerphile 频道讲解浮点数（Floating Point）的经典视频，把这个问题从二进制的地基上彻底讲透：计算机如何在有限比特里表示无穷多的实数、IEEE 754 标准的精妙设计、历史上真实发生过的浮点灾难，以及到今天仍在发生的工程陷阱。在 AI 大量替人写代码的 2026 年，这个老话题反而更重要了——因为 AI 生成的代码同样会犯浮点错误，而审查代码的你必须有能力看出来。

## 一、视频基本信息

| 项目 | 内容 |
|------|------|
| 视频标题 | Floating Point Numbers（浮点数） |
| 频道 | Computerphile |
| 讲解方式 | 白板手绘推演，面向工程师的直观讲解 |
| 发布时间 | 2014 年前后（据频道页面；本次未能实时抓取精确日期） |
| 播放量 | 数百万级，长期占据 "floating point" 主题搜索头部位置（据频道页面） |
| 视频地址 | 见文末参考来源 |

> 说明：本次制作时视频页数据抓取受限，以上热度信息为频道长期公开数据之约数，精确数字请以频道页面实时显示为准。

Computerphile 是英语世界最著名的计算机科普频道之一，与 Numberphile 同源，常驻讲解者多为诺丁汉大学的计算机学者。它的特点是不用炫酷动画，就靠一块白板和一支马克笔，把计算机科学里最"硬"的概念讲到工程师真正能听懂。浮点数这期正是这种风格的代表作：十几分钟，从"计算机只有 0 和 1"推到"为什么 0.1 存不进去"，全程没有一句废话。

## 二、核心主张拆解：视频到底讲了什么

### 要点一：无限的实数，有限的比特

视频的起点是一个根本性矛盾：实数在数轴上是**稠密且无限**的——任意两个实数之间还有无穷多个实数。而计算机里一个 `float` 只有 32 位，一个 `double` 只有 64 位。32 位最多表达 2³² ≈ 43 亿种状态，你不可能用有限个格子去装无穷个数。

所以浮点数的本质不是"精确表示"，而是**"在数轴上撒一把有限且精心挑选的点"**，所有真实数值都被就近吸附（舍入）到这些点上。理解了这一点，后面所有"怪事"都不怪了：误差不是 bug，是这个模型与生俱来的属性。

视频先讲了直觉上最容易想到的方案——定点数：固定小数点位置，比如 16 位整数部分 + 16 位小数部分。问题立刻暴露：定点数要么覆盖范围小，要么精度粗，二者不可兼得。想同时表示"原子的直径"和"星系的直径"，定点数在固定比特数下无能为力。这就引出了真正的答案。

### 要点二：把科学计数法搬进二进制

人类在纸上处理极大或极小的数，用的是科学计数法：光速写成 2.998 × 10⁸ m/s。我们只关心三样东西：正负号、指数、有效数字。视频的核心动作，就是把这套思想翻译成二进制，这就是**浮点数**（floating point）名字的由来——小数点的位置不再固定，而是"浮"着，由指数字段动态决定。

IEEE 754 单精度（32 位）的内存布局是视频反复画、也是最值得记住的一张图：

```
| 1 bit 符号 | 8 bit 指数 | 23 bit 尾数 |
```

双精度（64 位）则是 1 + 11 + 52。三项的含义：

- **符号位**：0 正 1 负；
- **指数**：存储的是"偏移后的指数"（biased exponent），比如单精度把真实指数加上 127 再存，这样指数字段无需单独的符号位就能表示正负指数，还能保持"按位比较大小"的硬件友好性；
- **尾数**：只存有效数字的小数部分，因为规格化后整数位永远是 1（如 1.xxxxx × 2ⁿ），这个恒为 1 的 leading one 不必存储，等于**白赚一位精度**——视频里特别强调了这种"抠比特"的工程智慧。

这套设计让 32 位浮点数既能表示约 10³⁸ 的巨大数，也能表示约 10⁻³⁸ 的微小数，动态范围碾压同样比特数的定点数。

### 要点三：0.1 的"死刑判决"——二进制里的无限循环

这是全片最关键的一幕。十进制的 0.1 写成二进制是 0.000110011001100…，**1100 无限循环**。原因和 1/3 在十进制里是 0.333… 完全同构：1/3 在十进制循环，1/10 恰好含因子 5、与二进制互质，于是必然无限循环。

而尾数只有 23 位（双精度 52 位），存储时必须在某处截断并舍入。所以：

```
>>> 0.1
0.1000000000000000055511151231257827
```

你在任何主流语言里写下的字面量 `0.1`，从来就不是 0.1，而是**比 0.1 略大（或略小）的最近的那个可表示数**。`0.1 + 0.2 !== 0.3` 的完整因果链是：0.1 被舍入过一次，0.2 被舍入过一次，两者相加的结果再次舍入，三个"最近邻"的误差叠加后，落在的格子与 0.3 的最近邻格子**不同**——于是 0.30000000000000004 诞生了。每一步都完全确定、可以逐位复现，没有任何随机性。

### 要点四：精度不是均匀的——格子会越变越大

视频里另一个容易颠覆直觉的点：浮点数的"刻度"在数轴上**不均匀**。指数每增加 1，可表示数之间的间隔（术语叫 ULP，Unit in the Last Place）就翻一倍。

- 在 1 附近，双精度数的间隔约为 2⁻⁵²，小到肉眼无法察觉；
- 在 10¹⁵ 附近，相邻两个双精度数已经相隔约 1——意味着再往上，`i + 1 == i` 这种"自增失效"会在某个大数处真实发生；
- 在极大值附近，间隔可能上百万。

一句话总结：**浮点数的精度是相对的，不是绝对的**。它保证的是"有效数字位数"大致恒定，而不是"小数点后多少位恒定"。这也是为什么金额计算用浮点数是错的——从 0.01 元到 10 亿元，用户要求的是绝对精度，而浮点只能给相对精度。

### 要点五：那些设计好的"逃生口"

视频收尾提到了标准里的特殊值设计：指数位全 1 配尾数全 0 表示 ±Infinity；指数全 1 配尾数非 0 表示 NaN（NaN 连自己都不等于自己：`NaN !== NaN`）；指数全 0 的非规格化数（denormals）负责填补 0 附近的精度真空，实现向 0 的渐进下溢。这些"格子"是 IEEE 754 特意留的逃生口，让硬件在不抛异常的情况下继续计算并携带异常状态——这是当年标准设计者深思熟虑的结果，也为后面讲设计史埋下伏笔。

## 三、知识与理论溯源：这一切从哪来

### IEEE 754 与 William Kahan

视频讲述的整套规范就是 **IEEE 754 标准**，1985 年正式发布，此后成为几乎所有 CPU 浮点单元（FPU）的事实基础。标准背后的灵魂人物是伯克利大学的 **William Kahan** 教授，人称"浮点数之父"，他主导的设计在 1989 年拿下图灵奖。Kahan 团队当年最激进的主张是：不同厂商的浮点运算必须有**可移植的、确定性的**结果——今天你在 x86 和 ARM 上算 `0.1 + 0.2` 得到完全相同的位模式，就是他留下的遗产。

值得一提的是，Kahan 至今仍在维护名为 "Prof. Kahan's papers" 的个人页面，持续抨击各语言编译器为性能牺牲浮点严格性（如允许 FMA 重排、x87 80 位中间精度）的行为。视频里"这标准比很多人想的严谨得多"的潜台词，出处就在这里。

### Goldberg 的传世论文

要往深处走，绕不开 1991 年发表、至今仍被无数大学课程列为必读的论文 **《What Every Computer Scientist Should Know About Floating-Point Arithmetic》**（David Goldberg，Sun 微系统）。论文用严格的误差分析证明：IEEE 754 在规格化范围内的运算满足**"半 ULP 舍入误差界"**，并系统阐述了为何"用浮点做数值算法"在数学上完全可控。本文批判性审查一节的很多结论，标准出处就是它。

### 三场真实的浮点灾难

视频本身偏原理，但理解其价值最好的方式是看历史代价：

1. **爱国者导弹拦截失败（1991 年，宰赫兰）**。系统以 0.1 秒为节拍计时，而 0.1 在二进制里无限循环，系统用 24 位定点数截断存储，每次产生约 0.0000001 秒的舍入误差。连续运行约 100 小时后，累计时钟漂移约 0.34 秒，足够让雷达预测的目标位置偏差近 700 米，拦截弹从假目标旁飞过，来袭飞毛腿命中兵营，28 名士兵死亡。事故调查报告（GAO/IMTEC-92-26）明确点名浮点/定点表示误差是根因——正是本文主题的第一滴血。
2. **阿丽亚娜 5 号首飞爆炸（1996 年）**。火箭升空 37 秒后自毁，损失约 3.7 亿美元。调查结论：惯性导航代码直接沿用阿丽亚娜 4 的模块，将一个 64 位浮点数转换为 16 位整数时溢出，未被捕获的异常触发火箭整体自毁。这是"浮点与整数表示边界"最昂贵的教训——一个在旧火箭数值范围内永远安全的转换，在新火箭更快的加速度下爆了。
3. **温哥华证券交易所指数（1982 年）**。指数计算用**截断**而非四舍五入处理小数，每次交易损失不到千分之一的"零头"，但几十万次交易累积下来，指数在约一年半内被低估近一半：真实约 1000 点的指数显示为 500 多点。它是"每次误差极小 × 海量迭代 = 巨大灾难"的教科书案例。

### 语言层的救赎与代价

正因二进制浮点对"十进制人类"天生不友好，主流语言都提供了十进制浮点/定点方案：Python 的 `decimal.Decimal`、Java 的 `BigDecimal`、C# 的 `decimal`（128 位十进制浮点）、数据库的 `NUMERIC` 类型。它们的代价是：运算通常比硬件浮点慢一个数量级以上（软件模拟 vs 专用电路）。Kahan 还发明了 **Kahan 求和算法**（compensated summation），用一个补偿变量把累加误差从 O(n·ε) 压到接近 O(ε)，Python 标准库的 `math.fsum` 就是其精密版实现。

## 四、批判性审查：哪些是真知，哪些被夸大

### 证据充分的部分

"0.1 无法被二进制精确表示"是可被逐位验证的数学事实，任何语言、任何硬件上一致复现，这部分毫无争议。IEEE 754 的内存布局、舍入规则（默认 round-to-nearest-even，银行家舍入）、特殊值语义，都有标准文本和四十年硬件实践背书。可以说这是计算机科学里**验证成本最低**的知识之一——你现在就可以打开终端亲眼看。

### 容易被带偏的部分

其一，**"浮点数是随机的/不可靠的"是常见过度引申**。恰恰相反，IEEE 754 的最大成就就是让浮点运算完全确定：同样的操作序列在任何合规硬件上产生完全相同的位结果。真正的问题是它**不满足人类对十进制精确性的直觉**，而不是它不稳定。视频若只讲到"你看，出错了！"就停，确实容易造成这种误读——这正是需要用 Goldberg 论文补上的一层：在误差界内使用浮点做科学计算，是完全严谨的工程实践，天气预报、流体力学的千亿行代码都建立在它之上。

其二，**"所以你应该到处用 Decimal"是流行但昂贵的建议**。十进制类型不是免费的：Python `Decimal` 比原生 `float` 慢一个数量级左右，且很多科学计算库（NumPy/PyTorch 底层）根本不支持。正确的工程判断是分场景：账务、计费、库存用整数最小单位（分）或 Decimal；图形渲染、机器学习、信号处理继续用 float——后者的世界本来就是近似的，GPU 甚至主动拥抱更低精度的 FP16/BF16 换取吞吐量。

其三，**视频年代较早，未覆盖的后续发展值得补充**。2019 年后 Python 3.12 之前 `repr` 已改为输出"最短往返表示"（所以现在打印 `0.1 + 0.2` 在 Python 里显示 `0.30000000000000004`，而 `0.1` 显示 `0.1` 而不是那串 0555…）；2022 年起 Intel x86 已逐步放弃 80 位 x87 中间精度路线，全面转向 SSE/AVX 与 IEEE 严格语义；AI 推理领域 FP8 格式也已进入实用。视频教的原理不变，但生态在演进，阅读时应有这个时间意识。

### 需要澄清的一个常见讹传

流传甚广的说法" floats 存钱会随机丢几分钱"并不准确——浮点误差是**系统性的、方向确定的**（二进制循环小数被舍入），不是随机噪声。这个区分很重要：因为系统性和确定性，它才可以被预测、被补偿（Kahan 求和）、被结构性规避（换十进制或整数）；如果是随机的，这些手段全都无效。把"确定性误差"误当成"随机不可靠"，会导致错误的工程决策（比如放弃 float64 去追求并不解决问题的"更高精度魔法"）。

## 五、认知提升与实用启示

### 启示一：给"相等判断"立一条铁律

从今天起，代码里永远不要出现 `floatA == floatB`。替代方案按优先级：绝对误差比较 `abs(a-b) < 1e-9`（数值范围已知且小时）；相对误差比较 `abs(a-b) <= epsilon * max(abs(a), abs(b))`（数值跨度大时）；Python 里直接用 `math.isclose()`，它两者兼顾且默认参数合理。把这条写进团队 code review 的 checklist——它是浮点错误里发生率最高、却最容易预防的一类。

### 启示二：货币计算的"三不原则"

不用 `float` 存金额；不做浮点除法后四舍五入再累加；不在数据库里用 `FLOAT/DOUBLE` 存钱。正确姿势二选一：以**最小货币单位整数**存储（美分/分），或用 `Decimal/NUMERIC`。一个实用的自检问题："这个数会有用户拿着计算器对账吗？"——只要有，就属于十进制世界，禁用二进制浮点。

### 启示三：AI 时代的特殊意义——你必须是那个看得懂的人

2026 年的现实是：大量代码由 AI 生成。但 AI 生成的代码恰恰高频复现人类语料里的经典错误——用 `float` 存价格、用 `==` 比较浮点、在循环里朴素累加上万次浮点数。这些代码**看起来能跑、测试还可能通过**（误差恰好小于断言阈值），上线后在真实数据规模下才爆雷。底层原理（哪些数在二进制里循环、误差往哪个方向漂移、累积速度多快）目前仍是人类工程师的护城河——AI 能写出 `Decimal`，但判断"这里必须用 Decimal"的责任在你。这条视频十几分钟的投入，本质上是在给你的代码审查能力上保险。

## 六、视频论证结构图

```mermaid
graph TD
    A[根本矛盾: 无限实数 vs 有限比特] --> B[方案一: 定点数]
    B --> B1{缺陷: 范围与精度不可兼得}
    B1 --> C[方案二: 二进制科学计数法<br/>= 浮点数]
    C --> D[IEEE 754 布局<br/>符号1 + 指数8 + 尾数23]
    D --> E[设计巧思: 隐藏首位1<br/>偏移指数127]
    D --> F[核心代价: 0.1 = 0.0001100...二进制无限循环]
    F --> G[舍入后: 0.1+0.2 = 0.30000000000000004]
    D --> H[间隔ULP不均匀: 精度是相对的]
    H --> I[大数吞小数: i+1==i 可复现]
    G & I --> J[工程防线]
    J --> K[epsilon比较 / math.isclose]
    J --> L[货币: 整数分 或 Decimal]
    J --> M[Kahan求和 / math.fsum]
    D --> N[特殊值逃生口: Inf / NaN / 非规格化数]
    N --> O[确定性成就<br/>任何硬件结果一致]
    O --> P[历史代价: 爱国者1991 / 阿丽亚娜5 1996 / 温哥华指数1982]
```

## 延伸阅读

1. **《What Every Computer Scientist Should Know About Floating-Point Arithmetic》**（David Goldberg，1991）——浮点数领域的"圣经"论文，视频所有原理的严格数学出处，Oracle/Sun 原版全文可免费阅读。
2. **Python 官方教程第 14 章 "Floating Point Arithmetic: Issues and Limitations"**——用最短的篇幅讲清 Python 视角的浮点表示与 `math.fsum`/`Decimal` 实践，标准库文档里的宝藏章节。

## 参考来源

1. [Computerphile — Floating Point Numbers（YouTube 频道页）](https://www.youtube.com/@Computerphile) — 本文解读的主视频；因本次采集环境网络受限，未能附上可直接核验的 watch 链接，请在频道内搜索标题获取，播放数据以频道页面实时显示为准。
3. [David Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic（Oracle/Sun 存档）](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) — 1991 年经典论文全文。
4. [Python 官方教程：Floating Point Arithmetic](https://docs.python.org/3/tutorial/floatingpoint.html) — 0.1 的表示、最短 repr 与 `math.fsum` 的官方说明。
5. [GAO 报告 GAO/IMTEC-92-26：Patriot Missile Time-Tracking Problem](https://www.gao.gov/products/IMTEC-92-26) — 1991 年宰赫兰拦截失败事故的官方调查报告，浮点/定点截断误差致 28 人死亡的原始出处。

---
> 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵**爱卫生**（微信ID: gprshome201101）维护，聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。
> 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
