编程世界有一个流传最广的“灵异事件”:在浏览器控制台里敲下 0.1 + 0.2,回车,屏幕上端端正正打出 0.30000000000000004。没有报错,没有警告,就是差了那么小数点后第十七位的万万分之几。所有主流语言——JavaScript、Python、C++、Java——在这个问题上整齐划一地“翻车”。有人第一次看到时以为自己电脑坏了,有人怀疑是编译器出了 bug,甚至有人认为这是“计算机不会算数”的铁证。
英国诺丁汉大学背景的老牌科普频道 Computerphile 的经典基础课 《Floating Point Numbers》(浮点数),就是专门来回答这个问题的。这支时长十来分钟的视频在程序员社区常年被反复推荐,是“计算机如何存数字”这个话题下流传最广的入门课之一(播放数据以频道页面显示为准)。它用一黑板的手写推导讲透一件事:这不是 bug,而是一份七十多年前就签好的、极其精明的工程契约。看懂它,你不仅知道 0.1 加 0.2 为什么不等于 0.3,还能看懂这个数字时代所有关于“精度”的暗礁——从导弹拦截失败、火箭爆炸,到今天 AI 芯片为什么抢着用更“粗”的数字格式。
| 项目 | 内容 |
|---|---|
| 视频标题 | Floating Point Numbers(浮点数) |
| 频道 | Computerphile(诺丁汉大学学者出镜的计算机科普频道) |
| 时长 | 约 10 分钟(以频道页面显示为准) |
| 发布时间 | 2010 年代中期上线,常年位列频道基础课推荐榜(以频道页面显示为准) |
| 播放量 | 百万级常青内容(以频道页面显示为准) |
| 核心主题 | IEEE 754 浮点标准:计算机为什么“算不准”小数 |
视频的开场就是那句灵魂拷问:0.1 加 0.2,为什么等于 0.30000000000000004?出镜讲师在纸上把这个数算给你看——0.1 在二进制下不是“一位小数”,而是一个无限循环小数:0.0001100110011001100…,"0011"这组数字永远循环下去。
这跟十进制里 1/3 = 0.333…是同一类病:某个数在你选的进制下除不尽。十进制里 1/3 写不完,是因为 3 不整除 10 的幂;而 0.1 写不完,是因为 10 的因子(2 和 5)里只有 2 能整除 2 的幂,5 不行。凡是分母含有 2 和 5 以外因子的十进制小数,在二进制下都是无限循环。而计算机的内存是有限的,无限循环数只能截断——误差从这一刻就注定了。
关键认知在这里发生了第一次反转:0.1 + 0.2 出错,不是“计算机算错了加法”,而是“你输入的两个数从一开始就没被精确存进去”。加法本身算得一点没错,它只是忠实地把两个近似值加起来,得到了一个近似的结果。
接下来视频进入正题:既然内存有限,数字该怎么存?答案就是每个程序员都听过、但很少真正拆开看过的 IEEE 754 标准。
它的思路和科学计数法一模一样:把一个数拆成三段——符号位(正负)、指数(阶码)、尾数(有效数字)。所谓“浮点”,浮的就是小数点:指数变一变,小数点就在二进制位里左右滑动,所以叫“浮动的小数点”。常用两种规格:
视频里最漂亮的一个细节是隐藏位技巧:二进制的科学计数法中,首位有效数字必然是 1(就像十进制科学计数法首位必然是 1~9),既然永远是 1,就不用存了——白赚一位精度。这 53 位(52 存储 + 1 隐藏)就是双精度的全部家当。
而为什么 0.1 + 0.2 恰好差“那么一点点”?因为双精度约合 16 位十进制精度,0.1 和 0.2 各自被舍入到第 17 位附近才对齐,误差累积后离 0.3 差了 0.00000000000000004——恰好卡在“能被打印出来”的边界上。这是精度悬崖的第一次露脸。
视频后半段讲了一个大多数入门材料都略过、但极其重要的图景:浮点数铺出来的数轴不是连续的,而是一把越往右齿距越大的尺子。
在 1.0 附近,两个相邻浮点数的间隔约为 0.0000000000000002(2 的负 52 次方量级),细得像发丝;但在 10 的 20 次方附近,相邻两个浮点数之间已经隔着一万六千多——比很多国家的 GDP 与你钱包的差距还大。数字每翻一倍,网格的缝隙也翻一倍。
这直接解释了几个著名怪象。第一,JavaScript 的整数悬崖:JS 的 Number 用双精度存所有数字,当整数超过 2 的 53 次方(约 9007 万亿),相邻可表示整数间距变成 2——9007199254740992 + 1 的结果还是 9007199254740992。这就是 JS 只有“安全整数”概念、2020 年才补上 BigInt 类型的原因。第二,游戏世界的坐标抖动:开放世界游戏里,角色离原点几万米后模型开始抽搐,因为坐标的浮点缝隙大到比人物鼻子还宽,引擎被迫改用“相机相对坐标”这类技巧来绕开它。第三,加法失去结合律:(1e20 - 1e20) + 1 等于 1,但 1e20 + (-1e20 + 1) 等于 0——括号位置不同,天差地别,因为 1 落进 1e20 的缝隙里,被直接吞掉了。
如果视频只讲到“计算机存不准小数”,它就只是又一条猎奇科普。它真正的价值在收尾处点破:浮点数不是随机的、不可理喻的错误堆,而是一份每个步骤都被严格定义的标准。
IEEE 754 对加减乘除和开方都做出承诺:每一步运算结果都是“正确舍入”的——即真实数学结果四舍五入到最近的浮点数,误差不超过半个最小格。它还统一规定了无穷大、NaN(非数)、次正规数(用来实现“渐进下溢”,让极小数字平滑趋零而非突然归零)的行为。NaN 甚至有个反直觉但极其实用的性质:它不等于任何数,包括它自己——所以 x !== x 是检测 NaN 的标准手法。
换句话说:浮点的每一小步都是确定的、跨平台一致的、误差有严格上界的。不精确,但可预算、可复现、可分析。这份契约,才是 1985 年那个标准文件真正的历史功绩。
视频里点到为止的每一条,背后都有一段真实的工程史。
威廉·卡汉(William Kahan)与 IEEE 754 的诞生。 这位加州大学伯克利分校的数学家是浮点标准的主要设计者,被业内称为“浮点之父”,并因此在 1989 年获图灵奖(据 ACM 官方记载)。在标准诞生前,每家计算机制造商都有自己的浮点格式——IBM 大型机甚至用十六进制规格化浮点,不同机器算同一个题能给出不同答案,数值分析学者要做大量“机器相关”的分析工作。卡汉在 1970 年代末先以顾问身份把这套设计思想注入英特尔 8087 数学协处理器,随后主持起草标准;IEEE 754 于 1985 年正式发布,此后 2008 年修订版收入了十进制浮点格式,2019 年完成最新一次修订(据 IEEE 官方标准文档序列)。今天全世界每一颗 CPU 的浮点单元,跑的都是这套规则。
误差分析的理论根基。 数值计算领域有一篇被引用最多的综述:大卫·戈德堡 1991 年的《What Every Computer Scientist Should Know About Floating-Point Arithmetic》(每个计算机科学家都应该知道的浮点运算知识)。它系统证明了浮点误差不是玄学,而是可以用“向前/向后误差分析”严格框定的量。视频讲的是现象,这篇论文讲的是现象背后的数学保证。
爱国者导弹的时钟漂移(1991)。 海湾战争中,驻扎在沙特达兰的美军爱国者防空系统连续开机约 100 小时后未能拦截一枚飞毛腿导弹,弹体命中营房,28 名美军士兵死亡(据美国审计总署事后调查报告,编号 IMTEC-92-26)。事故根源:系统内部用 24 位定点数近似 0.1 秒的时钟节拍,累积漂移约 0.34 秒,据此推算的目标空中位置偏差数百米,雷达“搜索窗口”扑空。注意——这是定点数的表示误差,与 IEEE 浮点是近亲而非同一种病,但病根一模一样:有限位数存不下某些数。
阿丽亚娜 5 号火箭首飞爆炸(1996)。 欧洲航天局的阿丽亚娜 5 号首飞约 37 秒后偏离轨道解体,自毁系统引爆,四颗卫星报废,损失以亿美元计(据事故调查委员会“狮子报告”)。直接原因:火箭复用了阿丽亚娜 4 号的一段对准程序,其中把一个 64 位浮点数(水平速度)转换成 16 位整数时——5 号飞得比 4 号快得多——数值超出 16 位整数上限,触发异常,两套惯性导航系统接连停机。这是类型转换溢出,不是精度丢失,又是另一种“数字格式”病。
英特尔奔腾 FDIV 查表错误(1994)。 数学家 Thomas Nicely 发现奔腾处理器算除法偶发出错,追查到浮点除法电路的查找表有五项被错误留空;风波之后英特尔宣布召回,为此计提约 4.75 亿美元(据当时财报与媒体报道)。这一课说明:即使有了 IEEE 754,硬件实现仍可能背离标准——契约签了,还得有人验收。
Excel 的 850×77.1=100000(2007)。 微软表格软件曾被用户发现这个著名的乘法错误:乘积本应是 65535,浮点运算把它算成 65534.999…,而 Excel 在把这个数转回十进制显示时,恰好在这个特殊数值区间走了岔路,屏幕上打出 100000(据微软当时的官方说明与修复公告)。这是显示层的 bug,底层数值其实没错——又一个被算在“浮点”头上的冤案。
AI 时代的新剧情:精度成了可交易的资源。 到今天,剧情又翻了一面:AI 芯片在主动追求更粗的数字。深度学习训练早已把 16 位浮点(FP16/BF16)当标配,英伟达 H100 之后的主流加速卡把 8 位浮点(FP8)做成硬件原生动员格式,DeepSeek 等团队也公开报告过用 FP8 完成大模型训练(据各厂商技术报告);推理端甚至开始探索 4 位格式。逻辑很直白:神经网络对“每个数多两位尾数”不敏感,而对算力和带宽极度敏感——精度变成了可以按需出售的预算。当年卡汉拼了命要“锁死”的确定性,如今成了工程师主动议价的筹码,这是视频拍摄时还没上演的续集。
把视频的主张和外部证据对照,可以划出一条清楚的分界线。
证据充分的部分:二进制表示不了 0.1、双精度约 16 位十进制、数轴网格随数值变大而变疏、JS 的 2^53 整数悬崖——这些都可以在任何一台机器上当场复现,属于计算机科学的基础事实,没有任何争议。
容易被夸大的部分是“计算机不会算数”这个流传甚广的印象。事实恰恰相反:IEEE 754 的每一次加减乘除都是正确舍入的,误差有严格上界,跨硬件结果一致。浮点数的“不可靠”,绝大部分不是标准失职,而是使用者把它当实数用的期待落空——就像尺子本来就刻到毫米,你非要说它量不准头发。戈德堡论文的核心论点正是:浮点误差可以被分析和控制,工程上完全“可靠”。
流传中最常见的张冠李戴也值得点名:爱国者事故是定点数节拍误差,阿丽亚娜是整数转换溢出,Excel 是显示层缺陷——三个著名“翻车现场”严格说都不是 IEEE 浮点精度本身的错,但在传播中统统被归进“浮点不靠谱”的筐里。理解它们的真实病理,比记住“计算机数学很玄”有用得多。
视频没讲的补充:标准本身在 2008 年就收入了十进制浮点(decimal128 等格式),专为金融场景设计;C# 的 decimal 类型底层就是十进制的。也就是说,“钱不能用浮点算”这个结论,标准委员会早就听到了,并且给了官方答案——只是多数教程忘了提。
第一,永远不要用 == 比较浮点数。 这是这份契约最直接的推论:既然每个数都是近似值,相等判断就该问“差得够不够小”,而不是“是否严格相等”。实践中用相对误差容差,例如 |a-b| ≤ max(|a|,|b|)×ε,或者直接用语言内置工具——Python 的 math.isclose() 就是为此而生。循环累加判断收敛、判断两个计算路径的结果是否一致,同理。
第二,钱的事,别交给二进制。 金额要么用最小单位整数(以“分”记账),要么用十进制类型(Python 的 decimal、Java 的 BigDecimal、C# 的 decimal)。商业系统里 0.1 + 0.2 ≠ 0.3 不是段子,是对账差一分钱查一夜的开始。同理,凡是“必须精确到某个整数刻度”的量——积分、库存、配额——都优先考虑整数或定点方案。
第三,大量累加时提防“大数吃小数”。 往一个已经很大的和里反复加小量,小量会掉进大数的缝隙里被吞掉。对策有三:把数按大小排序、先加小的;分块求和再合并;或者直接用 Kahan 求和算法——用一个补偿变量把每次被舍入丢掉的零头记下来、下次找补回来,几十行代码就能把百万次累加的误差压低几个数量级(这正是卡汉本人的手笔)。科学计算默认上双精度,不要为了省内存用单精度存中间结果——除非你清楚自己在交易什么。
Kahan 求和的骨架:
sum = 0; c = 0 # c 记录每次被丢掉的零头
for x in numbers:
y = x - c # 先把上次的零头找补回来
t = sum + y
c = (t - sum) - y # 本次又被丢掉多少,记下来
sum = t
第四,把精度当预算来花。 这是最新的一条认知:精度不是越高越好,而是成本结构的一部分。写图形、训练模型、跑推理时,主动选择 FP16/FP8 换吞吐;写结算、导航、科学仿真时,坚决上十进制或双精度。看清每个数字格式的“刻度”和“价格”,才是这条视频最终极的读懂方式——从“为什么算不准”进化到“我该让它多准、为此付多少”。
flowchart TD
A["0.1+0.2≠0.3"] --> B["十进制小数在二进制下除不尽"]
B --> C["有限位数必须舍入"]
C --> D["IEEE 754:符号+阶码+尾数"]
D --> E["隐藏位白赚一位精度"]
D --> F["数轴网格越往右越疏"]
F --> G["2^53 整数悬崖"]
F --> H["大数吃小数"]
C --> I["历史翻车:爱国者/阿丽亚娜/Excel"]
D --> J["正确舍入:误差有上界"]
J --> K["工程结论:误差可预算"]
E --> K
G --> K
H --> L["对策:容差比较/十进制/Kahan求和"]
K --> L
K --> M["AI时代:精度当预算花"]
本文由「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 等场景全覆盖。
