几乎每个程序员都在深夜的调试器里撞见过它:0.1 + 0.2 的结果不是 0.3,而是 0.30000000000000004。多数人的处理方式是背一句口诀——“浮点数不精确”——然后继续写代码,心里的疙瘩从没解开过。Computerphile 频道的经典一课《Floating Point Numbers》就是为这个疙瘩准备的。主讲人 James Clewett 用十分钟左右,从“计算机只有0和1”这个最原始的约束出发,一步步推演出 IEEE 754 浮点数的完整设计:为什么要有尾数和指数、为什么 0.1 天生存不下、为什么 NaN 连自己都不等于自己。它解决的不是“怎么绕过浮点数”的技巧问题,而是“浮点数到底是什么”的认知问题——看懂这一课,你写下的每一个 float 和 double,在脑中都会有清晰的形状。选它做本期解读,一是这个主题在中文技术内容里长期被讲糊,二是它是那种十年不过期的常青课:语言会过时,二进制的数学不会。
| 项目 | 内容 |
|---|---|
| 视频标题 | Floating Point Numbers – Computerphile |
| 频道 | Computerphile(计算机科普名频道,订阅约190万) |
| 出镜 | James Clewett(物理学者出身,频道常驻主讲人) |
| 上线时间 | 2014年(常青经典内容) |
| 时长 | 约10分钟 |
| 播放量 | 约150万+(截至2026年9月,约数) |
| 核心主题 | IEEE 754 浮点数的原理、精度陷阱与工程对策 |
说明:以上时长与播放量为约数,以 YouTube 页面实时数据为准。本视频不足15分钟,但信息密度足够,且主题属于程序员“人人踩过、少有人讲透”的硬骨头,故仍入选本期解读。
【视频】视频没有直接抛出 IEEE 754,而是先讲了个更朴素的问题:计算机里只有0和1,想表示"3.625"这种小数,最直观的办法是什么?把二进制位一分为二——点左边是整数部分,点右边是二分、四分、八分……这就是定点数(fixed-point)。但它有个致命矛盾:位数固定时,给了整数部分,小数精度就得让路;想要高精度小数,能表示的数又变得极小。整个“可表示数的范围”被焊死在一个尺度上。Clewett 用这个困境自然引出了正题:能不能让小数点的位置本身也变成数据的一部分?【延伸】定点数并没有消失——嵌入式芯片、FPGA、数据库的 DECIMAL 类型,本质都是定点思路的现代版本。理解它的缺陷,正是理解浮点设计动机的钥匙。
【视频】视频的核心一步,是把十进制的科学记数法 1.234 × 10³ 平移到二进制世界:任何数都能写成 1.01101 × 2ⁿ 的形式。这下清楚了——存储一个数,只需要三样东西:正负号、指数 n、以及小数点后的那串尾数(mantissa)。这就是“浮点”名字的由来:小数点的位置由指数决定,可以浮动,同一个 32 位格式既装得下 10⁻³⁸ 的尘埃,也装得下 10³⁸ 的星系。范围问题就此破解:精度不再和范围打架,而是各管各的。【延伸】这个“指数与尾数分离”的思想其实遍布计算机科学:游程编码、对数坐标、甚至数据库索引里的分层,都是在“动态范围”和“局部精度”之间做交换。浮点数只是这套思想最成功的应用。
【视频】接下来是全片我最喜欢的一个细节:二进制规格化之后,尾数的首位永远是1(就像十进制科学记数法整数部分永远是1到9)。既然永远是1,那存它干什么?IEEE 754 干脆不存这个首位的1,23位尾数实际携带24位精度——凭空多出一位,被称为隐藏位(implicit leading 1)。Clewett 强调,这不是小聪明,而是规格化的副产品红利。算下来,32 位 float 的真实画像是一条 32 位拼图:1 位符号、8 位指数、23 位尾数(实际 24 位),能提供约 7 位十进制有效数字;64 位 double 则约 15-16 位。【延伸】这也是为什么把 float 强转 double 永远无损、反向则可能丢精度——位宽决定了尾数栅格的疏密。
【视频】现在可以正面回答那个著名问题了。十进制的 0.1 在二进制里是什么?视频演算给你看:0.1 = 0.000110011001100…,1100 无限循环——就像十进制写 1/3 = 0.333… 一样,二进制根本写不尽 0.1。所以浮点数存下的从来不是 0.1,而是“最接近 0.1 的那个有限位数的数”。当你写 0.1 + 0.2,实际是两个近似值相加,各自的舍入误差在结果里露了头,得到 0.30000000000000004。这不是 Python、JavaScript 或任何一门语言的 bug,而是 IEEE 754 的必然——所有遵循标准的语言一起“错”,错得整整齐齐。【延伸】反过来说,0.5、0.25、0.75 这些分母是 2 的幂的小数,在浮点里是绝对精确的。判断哪些小数危险,看它能不能写成 n/2ᵏ 就够了。
【视频】如果只带走一个观点,我选这个。视频指出,浮点数在数轴上不是等间距排列的——在 1 附近,相邻两个 float 的间隔约是一亿分之一;到了十亿量级,间隔会膨胀到几十上百。因为精度被尾数位数锁死,而指数是乘法性的,所以浮点数的“刻度线”在 0 附近密如筛网,越往大数方向越稀疏。这意味着:float 里能精确表示的数,总数量是有限的(32 位 float 只有约 2³² 个),而它们中间夹着的无限多个实数,都会被“吸附”到最近的刻度线上。【延伸】这解释了无数灵异 bug:在浮点里给一个大数加 0.001 可能毫无效果(间隔比你加的数还大);游戏运行几小时后坐标漂移(误差累积超出了该区域的刻度密度);两个看似不同的输入算出同一个结果。浮点数不是“模糊的数”,而是一张疏密不均的精确栅格——这个视角比“浮点不精确”准确一百倍。
【视频】8 位指数有 256 个编码,IEEE 754 保留了两端:全 0 和全 1。全 1 的指数配合 0 尾数是 ±Infinity,配合非 0 尾数就是 NaN(Not a Number)。于是浮点体系拥有了完整的“错误表达 vocabulary”:除零不崩溃,而是得到 Infinity,让计算继续流动;非法操作得到 NaN,并且 NaN 与任何值比较都返回 false——包括它自己,NaN == NaN 是 false。这设计初看反人类,实则是深思熟虑:NaN 不等于任何数,等于在告诉你“这个值已经污染了,别再基于它做判断”。【延伸】还有个更冷门的居民:-0.0。负零在比较时等于 +0.0(-0.0 == 0.0 为 true),但 1/-0.0 得到 -Infinity。在物理仿真里,负零承载着“从负方向趋近”的信息,弄丢它会改变数值微分的结果。这些都是“刻度栅格”边界上的精细设计。
【视频】单次舍入误差约 10⁻⁸ 级别,看似无害,但视频提醒:误差会累积。一亿次浮点加法,误差完全可能爬升到可见量级——这正是金融、导航、科学计算领域对浮点严防死守的原因。更凶险的是“大数吃小数”(cancellation):把 0.001 加进 10 亿,结果在浮点栅格上被吸附回原值,小数凭空消失;两个相近的大数相减,前段位互相抵消,留下来的全是误差,有效数字瞬间归零。【延伸】工程界为此发明了一整套“误差管理兵法”:Kahan 求和算法用一个补偿变量把每次被丢弃的误差捡回来;成对求和(pairwise summation)用分治限制误差增长;NumPy 和各大 BLAS 库默认就在用这些技巧。你调用的 sum() 之所以比手写循环稳,背后全是这些兵法。
【延伸】视频聚焦原理,没有覆盖 2026 年浮点最火热的战场,这里补上。3D 游戏每一帧要做百万级矩阵乘法,GPU 的整个设计哲学就建立在浮点运算的可并行性上;而 AI 大模型更是把“降低精度”变成了战略:训练从 float32 走向 bfloat16,推理端 NVIDIA 的 Tensor Core 已经把 fp8 甚至 fp4 变成标准配置。方向和直觉相反——低精度不是无奈妥协,而是吞吐杠杆:位宽减半,显存占用、带宽需求、计算能耗近乎同步减半,换来的是更大 batch、更快推理。代价则是更精细的误差控制(损失缩放 loss scaling 等技术应运而生)。从 IEEE 754 的“精确栅格”视角看这一切会豁然开朗:bfloat16 砍掉的是尾数(栅格变稀),保住的是指数(范围不变)——所以它对深度学习友好,因为神经网络怕“数值溢出”远胜过怕“精度变粗”。
flowchart TD
A["计算机只有0和1"] --> B["定点数<br/>小数点位置固定"]
A --> C["浮点数<br/>IEEE 754标准"]
B --> B1["痛点:范围与精度<br/>此消彼长"]
C --> D["符号位:1位定正负"]
C --> E["指数位:偏移码存储"]
C --> F["尾数位:隐藏首位1"]
E --> G["保留编码:<br/>Infinity与NaN"]
F --> H["0.1在二进制里<br/>是无限循环小数"]
H --> I["经典陷阱:<br/>0.1+0.2不等于0.3"]
C --> J["数轴真面目:<br/>疏密不均的精确栅格"]
I --> K["对策一:<br/>金额用整数或Decimal"]
I --> L["对策二:<br/>容差比较代替=="]
I --> M["对策三:<br/>Kahan求和控累计误差"]
J --> N["前沿应用:<br/>图形与AI低精度换吞吐"]
看完这课,可以立刻落成四条写码守则:
第一,钱永远不进浮点。 存储金额用整数“分”,或用语言内置的 decimal 类型(Python 的 decimal、Java 的 BigDecimal)。这不是洁癖:浮点的累计误差配合四舍五入规则,长年累月跑下来,账目差出几块钱是完全真实发生过的事故。
第二,浮点比较必须带容差。 把 a == b 换成 abs(a - b) < 1e-9(量级大时改用相对误差)。测试用例里的浮点断言尤其要如此,否则 CI 会在某天因为末位抖动随机变红。
第三,警惕求和顺序与中间溢出。 大数组求和交给成熟的库(NumPy 内部有补偿机制);手写时先除后乘、避免中间值超出量级;“相近大数相减”能重写成等价形式就重写。
第四,读懂数据类型的“精度预算”。 float 约 7 位有效数字、double 约 15-16 位——先估算你的问题需要几位,再选类型。做 AI 推理的读者不妨顺手研究 bfloat16 的取舍,你会发现教科书(这期视频)和工业前沿(fp8 时代)讲的是同一套栅格逻辑,只是切口粗细不同。
认知层面最大的升级是那句重新定义:浮点数不是“不精确的数”,而是“疏密不均的精确栅格”。建立这个模型后,你不再需要背口诀,而是能自己推理出任意场景下误差会出现在哪。
这个视频最大的价值,是把“浮点数不精确”这句以讹传讹的口诀,还原成一幅可推理的地图:一张由指数决定疏密、由尾数决定分辨率的精确栅格——看懂它,你就永远知道哪些数踩得实、哪里是深渊。
本文由「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 等场景全覆盖。
