几乎每个程序员都在职业生涯的某个深夜撞见过它:0.1 + 0.2 的结果不是 0.3,而是一串可疑的 0.30000000000000004。多数人的处理方式是背一句口诀——“浮点数有精度问题”——然后绕着走。但口诀解决不了下一次的决策:为什么乘法有时反而“对”了?为什么金额绝对不能用它?为什么导弹和火箭会栽在这上面?
在 YouTube 上把这个话题讲得最透的,是 Computerphile 频道那块经典白板课 《Floating Point Numbers》。它不是最新鲜的一期视频,却是这个话题被引用得最多、被程序员社群反复翻出来补课的一期——主讲人在十几分钟里,从“计算机为什么存不下 0.1”一路推到 IEEE 754 的位级布局,全程只靠一块白板,没有任何炫技剪辑。看完这条视频,你会明白:浮点数不是“算错了”,而是一套自洽到近乎冷酷的精确系统,它的“不准”从数字被存进去的那一刻就已经注定。
本文按视频的叙事顺序拆解其核心内容,补上视频没讲到的历史事故、标准细节与实践对策——看过视频的读者能多懂一层,没看过的读者等于看完了。
| 项目 | 内容 |
|---|---|
| 视频标题 | Floating Point Numbers - Computerphile(浮点数) |
| 频道 | Computerphile |
| 主讲 | 频道常驻讲师(白板课形式,据频道页面) |
| 发布时间 | 2014 年(据频道页面存档信息) |
| 热度数据 | 播放量逾百万级、长期位居该话题检索首位(据频道页面历史显示,本文未做实时核验) |
| 形式 | 白板讲解,约一刻钟量级 |
注:以上热度与时间信息以频道页面历史资料为准,本篇未做逐日实时核验;视频可通过文末参考来源的频道页检索直达。
视频的论证是一条极干净的推导链,总共五步。
第一步:小数在二进制世界里是“外来移民”。 计算机里只有 0 和 1。二进制能精确表示的小数,只有那些分母是 2 的幂的分数——1/2 存得下(0.1₂)、1/4 存得下(0.01₂)、3/8 也存得下(0.011₂)。但 0.1(十进制)的分之一是 1/10,分母 10 = 2 × 5,含有那个二进制世界根本不存在的质因子 5。于是 0.1 写成二进制是一个无限循环小数:0.000110011001100…。视频里主讲人在白板上一路竖除下来,循环节 "0011" 反复出现——这就是一切问题的源头:不是计算机算错,是这个数在二进制里根本写不完。
第二步:定点数救不了场。 最直觉的方案是“定点数”:固定小数点的位置,比如 32 位里前 16 位放整数、后 16 位放小数。视频指出这样做两头堵——想表示天文学数字,小数位浪费着;想表示微观物理量,整数位浪费着。科学计算需要的,是同一种格式既能装下银河系的质量,又能装下一个电子的电荷。
第三步:把科学计数法搬进二进制。 十进制用科学计数法 6.02 × 10²³ 解决了“又大又小”的问题,二进制照搬:任何数都能写成 1.xxxxx × 2ⁿ 的形式。这一步里藏着一个漂亮的观察——规范化之后,整数位永远是 1,既然永远是 1,就不用存它了。这就是“隐藏位”(hidden bit)技巧:23 位的存储空间,实际装下了 24 位的精度,白赚一位。
第四步:把三段信息塞进 32 位。 视频随后画出 IEEE 754 单精度浮点数的经典位图:1 位符号 + 8 位指数 + 23 位尾数。指数不直接存负数,而是加了 127 的偏移,让 hardware 只做无符号比较;尾数存小数点后的那串二进制。64 位双精度同理放大:1 + 11 + 52。讲到这一步,视频实际上已经回答了“浮点数在内存里到底长什么样”这个多数程序员从未亲眼看过的问题。
第五步:舍入是宿命,不是故障。 无限循环的 0.1 装进有限的 23 位尾数,必须在某一位被“咔掉”并舍入。误差不是运算过程中产生的,而是在存储那一刻就被写死了。所以 0.1 + 0.2 ≠ 0.3 的本质是:你加的根本不是 0.1 和 0.2,而是它们各自的“最近似替身”,两个替身相加的结果又不恰好是 0.3 的替身。
这五步构成一个完整闭环:二进制的质因子缺陷 → 科学计数法方案 → 位级布局 → 舍入不可避免 → 你看到的每个“怪结果”都有唯一确定的原因。
IEEE 754 与 William Kahan。 视频讲的这套格式不是某个公司的私有约定,而是 1985 年颁布的 IEEE 754 标准。它背后的关键人物是伯克利的 William Kahan——他主导设计了这套被后世称为“数值界公制统一”的规范,让同一行 Fortran 代码在 IBM、Intel、Motorola 上算出位级一致的结果,并因此获得 1989 年图灵奖。隐藏位、偏移指数、渐进式下溢这些精巧设计都是他的手笔。看视频时记住这个名字,你会明白你每天在用的 float 和 double 背后站着一位图灵奖得主。
Goldberg 的经典论文。 1991 年 David Goldberg 在 ACM Computing Surveys 上发表《What Every Computer Scientist Should Know About Floating-Point Arithmetic》,至今仍是这个领域被引用最多的综述。视频里“舍入不可归咎于机器”的立场,论文里有更严格的表述:浮点系统是确定性的——同样的输入永远给出同样的输出,它不“准”,但它极其“稳”。这句话值得每个工程师刻进脑子。
爱国者导弹:累积误差夺走 28 条命。 1991 年 2 月 25 日,沙特达兰,一套爱国者防空系统因拦截失败,一枚飞毛腿导弹击中美军军营,28 名士兵死亡、近百人受伤。事后美国审计总署(GAO)的调查报告(IMTEC-92-26)给出了一个令所有程序员脊背发凉的原因:系统时钟以 0.1 秒为单位累加,而 0.1 无法被系统的 24 位浮点格式精确表示——每 0.1 秒产生一点舍入误差,连续运行约 100 小时后,时钟累积漂移约 0.34 秒,足以让弹道预测窗口整体错位数百米。视频讲的“存储那一刻的舍入”,在这里直接变成了战争史上的伤亡数字。
阿丽亚娜五号:另一种死法。 1996 年 6 月 4 日,欧洲航天局阿丽亚娜五号火箭首飞,升空约 37 秒后自毁,四颗 Cluster 科学卫星化为火球,损失以亿美元计。调查委员会(Lions 报告)认定的原因是一行类型转换:把一个 64 位浮点数转成 16 位有符号整数时发生溢出。这里有一个“多懂一层”的关键区分——阿丽亚娜栽的不是浮点精度,而是浮点与整数之间的格式转换;爱国者栽的是累积精度,阿丽亚娜栽的是超范围转换。症状相似,病根不同,处方也不同。
JavaScript:你天天在用的双精度。 视频讲的单/双精度格式,就是 JavaScript 中唯一的数据类型 Number 的真身——JS 没有整数类型(BigInt 出现之前),所有数字都是 IEEE 754 双精度。这解释了几个著名事实:安全整数上限是 2⁵³−1(Number.MAX_SAFE_INTEGER = 9007199254740991),因为 52 位尾数加上隐藏位恰好能精确存 53 位整数;0.1 + 0.2 === 0.30000000000000004;而一个更冷的知识是 0.1 + 0.3 的结果恰好显示为 0.4——两次舍入的误差在上街四舍五入时正好相互抵消。误差的存在与误差何时“露脸”,是两件事。
证据充分的部分。 视频关于二进制小数展开、规范化、位级布局、隐藏位的全部内容,属于教科书级共识,与 IEEE 754 标准文本和 Goldberg 论文完全一致,可以放心全盘接收。
视频省略的部分。 十几分钟的白板课不可能面面俱到,以下三处是它刻意简化的地方,也是读者可以“多懂一层”的地方:
一个常见误读需要纠正。 网上流传“计算机的数学不可靠”,这是错的。IEEE 754 是一个确定性、可复现、经过三十余年实战检验的精密系统;问题不在系统不可靠,而在实数到有限位数的映射必然有损。把“浮点不准”升级为“浮点不可信”,会导致错误的工程决策——比如明明该用浮点的图形计算、机器学习场景吓得改用十进制库,白白损失几十倍性能。
一个真实的路线争议。 标准本身在 2008 年修订时纳入了十进制浮点(decimal64/128),金融与税务系统普遍采用十进制路线以对齐人类的十进制账目习惯;而科学计算界坚持二进制路线,因为硬件原生支持、速度快得多。两条路线都是 IEEE 754 的正统,选哪条取决于你的数字是“钱”还是“物理量”。视频限于篇幅没有涉及这层分歧。
数据注记。 本文所引视频热度与发布时间为频道页面历史信息,未经实时核验;历史事故的数字(如 0.34 秒时钟漂移、28 人死亡)均出自 GAO 与 ESA 的公开调查报告,属于严肃文档记录,而非自媒体转述。
第一条:金额永远不要用浮点存。 这是这条视频最直接的工程推论。0.1 存不精确,意味着任何以元为单位的浮点金额都在悄悄漂移,对账时差异会以分甚至角为单位冒出来。正确做法二选一:用最小货币单位的整数(以“分”记账),或用十进制类型——数据库的 DECIMAL、Java 的 BigDecimal、Python 的 decimal.Decimal。判断标准很简单:这个数字会被人眼逐个核对(钱、成绩、配额),就用十进制或整数;这个数字只被机器消费(坐标、权重、概率),浮点就是正确工具。
第二条:浮点比较永远不用 ==,且大数要用相对误差。 判断两个浮点是否“相等”,用 Math.abs(a - b) < Number.EPSILON 这类容差比较。但要注意视频推导出的另一层:可表示数的间距随数量级增大,固定的 epsilon 在大数面前形同虚设。更稳的写法是相对误差:|a−b| ≤ ε × max(|a|, |b|)。一次写作习惯的改变,能免掉无数“为什么这个循环不终止”的深夜排查。
第三条:大量浮点累加要防漂移,学会两个武器。 把上百万个浮点数依次加进同一个累加器,舍入误差会单向累积——爱国者导弹的教训在数据分析里每天重演。两个对策:一是成对求和(pairwise summation,先两两相加再汇总, many 数值库的默认),二是 Kahan 补偿求和(用一个补偿变量把每次被舍掉的部分捡回来),后者正是图灵奖得主 Kahan 为解决他自己设计格式的舍入问题而发明的算法——标准的设计者最清楚标准的伤口在哪里。日常版本里,还有一个零成本技巧:累加前先把数据从小到大排序,让小数不至于被大数完全吞掉。
flowchart TD
A[二进制只有 0 和 1] --> B[只能精确表示<br/>分母为 2 的幂]
B --> C[0.1 含质因子 5<br/>变成无限循环]
C --> D[定点数两头堵]
D --> E[借来科学计数法<br/>1.x 乘 2 的 n 次]
E --> F[整数位恒为 1<br/>隐藏位白赚精度]
F --> G[三段式入库<br/>符号加指数加尾数]
G --> H[无限循环塞进有限位<br/>存储即舍入]
H --> I[个案<br/>0.1 加 0.2 不等于 0.3]
H --> J[累积<br/>导弹时钟漂移夺命]
H --> K[溢出<br/>火箭格式转换自毁]
I --> L[对策<br/>金额走整数或十进制]
J --> L
K --> L
L --> M[大数比较用相对误差<br/>累加用补偿求和]
本文由「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 等场景全覆盖。
