SICI · 天机门 · 第十一章

算理精微

数字运算的精密之道 · 筑基五层

你以为算术就是1+1=2。但0.1+0.2在计算机里不等于0.3。这不是bug——这是有限位表示无限的代价。

门楣刻字:「你以为0.1+0.2=0.3。记下了。」
序章

算经阁

0.1 + 0.2 等于几?

算经阁的门没有锁,只有一道刻痕——上面写着「算」字,笔画被人用指甲刮出了一道额外的裂痕。

算经阁内悬浮的 0.1+0.2 算式与 ALU 五大模块光影

诺依曼老祖站在门前,手边悬着一枚算珠,珠面上刻满了 0 和 1。

诺依曼老祖 诺依曼老祖

「你学过加法器。半加器、全加器、行波进位——ch02 的东西。但你可知,三十二位的进位要传多久?0.1+0.2 又等于几?」

精度之蚀 精度之蚀

「你以为0.1+0.2=0.3。记下了。」

0.1 + 0.2 浮点精度预测挑战器

先下注,再探索 ALU 全貌。算珠在你手里微微发烫。

先回答:0.1 + 0.2 等于几?
详解 · K11-07 浮点表示:为什么 0.1 永远装不进二进制

0.1 在二进制里是无限循环小数 0.0001100110011…——就像 1/3 在十进制里是 0.333…。单精度只有 23 位尾数,存到第 23 位必须斩断,斩断处就是误差之源。所以 0.1+0.2≠0.3 不是 bug,而是「用有限位表示无限」的先天代价:0.1 实际存成约 0.100000001490116,0.2 存成约 0.200000002980232,相加的舍入结果与 0.3 的存储值差了一个尾数最低位。

第一幕 · 自信

进位阵 · 延迟之墙

加法不是瞬时的——进位要一级一级传过去

石径尽头是一面墙——墙上刻着三十二个格子,每个格子里坐着一个全加器,首尾相连。墙的底部有一只钟,钟面上没有数字,只有一圈刻度。

诺依曼老祖 诺依曼老祖

「ch02 的行波进位加法器。三十二位,进位从最低位传到最高位,要穿过三十一级门。你猜,要多久?」

精度之蚀 精度之蚀

「你以为加法是瞬时的。等你看见钟面,就知道你等的不是加法,是进位。」

进位链延迟曲线追踪器

切换加法器模式 → 测量门延迟 → 对比硬件代价

门延迟: —

三种加法器延迟-面积对比

详解 · K11-01 快速进位:行波、旁路与选择的代价账

行波进位(RCA)的进位要一级级穿过全部全加器,32 位约 62 级门延迟。进位旁路(CBA)发现:若一组内所有位都「传播」进位(a⊕b=1),整组可一键跳过——延迟降到约 18 级。进位选择(CSA)更激进:高组同时按 Cin=0 和 Cin=1 各算一份,低组进位一到,用 MUX 二选一——约 12 级。代价是硬件:旁路加一组判跳逻辑,选择直接翻倍加法器。面积换速度,从这里开始。

第一幕 · 自信

补码殿 · 加减归一

一座加法器搞定加减——补码把减法翻译成加法

殿中央悬着一台加法器。老祖在加法器旁边放了一面镜子。镜子里是同样的加法器,但输入端的 B 被取了反。

诺依曼老祖 诺依曼老祖

「减法 A−B。你打算怎么算?提醒你——计算机里只有加法器。没有减法器。」

补码加减统一电路 · Sub 信号 + XOR 门 + C₀

切换 Sub 信号 → 观察 XOR 取反与 C₀ 进位 → 验证加减归一

Sub 信号:
C₀ 进位输入: 0
结果:—
溢出检测 V = C₃₁ ⊕ C₃₂: V = 0
详解 · K11-02 补码:减法如何被「翻译」成加法

模 2ⁿ 世界里,−B ≡ ~B+1(取反加一)。于是 A−B = A+(~B+1):一个 Sub 信号分两路——控制 B 的每一位过 XOR(Sub=1 时取反),同时接进位输入 C₀(Sub=1 时 +1)。加法器原封不动,减法和负数以零额外硬件入伙。溢出判定也随之统一:V = C₃₁⊕C₃₂(最高两位进位异或),正正得负、负负得正即溢出。

第一幕 · 崩塌

乘除塔 · 整数之限

七层算法全通,却连 0.1 都输不进去

乘除塔是一座七层石塔。每一层都刻着一种算法。你抬头看塔顶——那里有一扇门,门上刻着一个问题:「1 ÷ 3 = ?」

诺依曼老祖 诺依曼老祖

「七层你全爬完,算术的整数侧就通了。」

精度之蚀 精度之蚀

「你走完了整数的全部七层。加法、减法、乘法、除法——全通了。但你连 0.1 都输不进去。」

整数乘除算法四选一推演器

依次点亮七层算法 → 推开门 → 撞上整数之限

详解 · K11-03/04 乘法:从阵列到 Booth 到树形压缩

阵列乘法器把 32×32 个部分积一次铺开,直观但进位链冗长。Booth 编码一次看两位(Radix-4 看三位),把连续 1 串压缩成一次加减,部分积减半。Wallace/Dadda 树再用保留进位加法器(CSA)把每层 3:2 压缩,延迟从 O(n) 降到 O(log n)。Dadda 与 Wallace 的差别只在「每层压到多少」的节俭策略——Dadda 用的加法器最少。

详解 · K11-05/06 除法:恢复、不恢复与 SRT 的 4.75 亿美元教训

恢复余数除法每步试减,减出负值就加回来(恢复)再移位;不恢复除法把「加回」合并进下一步,省一拍。SRT 除法用商数字集 {−1,0,1} 和一张查表,每步可得多位商——1994 年 Pentium 的 FDIV bug 正是这张表有 5 个条目载入错误,罕见但真实地算错除法,Intel 最终花 4.75 亿美元召回。形式化验证从此成为浮点硬件的标配。

第二幕 · 挣扎

浮点台 · IEEE 之镜

有限位制造一套自洽的数系——三个区间各有使命

裂缝的另一侧是一面巨大的镜台。镜面上刻着三十二个格子——1 个符号位、8 个指数位、23 个尾数位。

诺依曼老祖 诺依曼老祖

「IEEE 754 单精度。你在 S0 猜错过 0.1+0.2 的地方——答案就刻在这面镜子上。先学怎么把一个实数装进这 32 个格子。」

IEEE 754 编码拆解 · 三区间探索

选择实数 → 逐位编码 → 探索三个区间 → 单双精度对比

详解 · K11-07/08 IEEE 754:一套自洽的有限数系

单精度 32 位 = 1 符号 + 8 指数(偏移 127)+ 23 尾数;双精度 = 1+11+52(偏移 1023)。规格化数暗藏前导 1,白赚一位精度。指数全 0 留给非规格化数与零——非规格化让 0 附近的值密度渐变(渐进下溢),而不是突然断崖。指数全 1 是 ∞(尾数 0)与 NaN(尾数非 0),让 1/0、0/0 有定义、可传播。这套设计 1985 年定型,至今统治所有通用计算。

第二幕 · 挣扎

对阶殿 · 消失的尾数

对阶是凶手——移出去的位永远消失了

殿中央悬着两根光柱——一根刻着 1.0×2⁰,一根刻着 1.0×2⁻²⁴。你想:1.0 + 0.0000000596... ≈ 1.0。很合理。

诺依曼老祖 诺依曼老祖

「不是『大约等于』。是『在 23 位尾数里,1.0×2⁻²⁴ 加上去之后,尾数最低位翻了吗?』把它们加起来。」

浮点加法四步 · 对阶 → 相加 → 规格化 → 舍入

拖动 B 尾数右移对阶 → 相加 → 选择舍入模式 → 观察 B 是否存活

A = 1.00000000000000000000000 × 2⁰
B = 1.00000000000000000000000 × 2⁻²⁴(对阶后)
详解 · K11-09 浮点加法四步:对阶、相加、规格化、舍入

① 对阶:小阶尾数右移,与大阶对齐——移出的位只能进保护位/舍入位/粘滞位(GRS)暂存。② 相加:尾数加减。③ 规格化:把结果调回 1.xxx 形态。④ 舍入:按舍入模式裁回 23 位。粘滞位记录「移出去的位里是否有过 1」,是正确舍入的关键。对阶右移是精度丢失第一现场——大数吃小数,就发生在这一步。

第二幕 · 再崩塌

舍入狱 · 误差雪崩

浮点不是实数的近似——它是另一套代数法则

舍入狱是一座环形石牢。牢中央悬着一只算盘——上面跑着一段循环:sum = 0; for (i = 0; i < 10000; i++) sum += 0.1;

精度之蚀 精度之蚀

「理论上 sum 应该等于 1000。你猜,实际上等于几?误差不会累积的——对吧?」

诺依曼老祖 诺依曼老祖

「你学的实数代数——结合律、分配律——在它的世界里全是废纸。」

浮点误差雪崩曲线追踪器

运行 10000 次累加 → 切换舍入模式 → 灾难性抵消 → 结合律违反

sum = 0; for (i = 0; i < 10000; i++) sum += 0.1;
灾难性抵消:1 − (1 − 10⁻⁸) = ? —
结合律违反:a=10³⁰, b=−10³⁰, c=10⁻³⁰
详解 · K11-11/12 舍入模式与数值稳定性

IEEE 754 规定四种舍入:最近偶数(默认,平局归偶,统计无偏)、向零、向 +∞、向 −∞。最近偶数让长序列累加的期望误差最小——这就是 10000 次 0.1 累加里四种模式分出高下曲线的由来。更深一层是数值稳定性:灾难性抵消(相近数相减,有效位瞬间清空)、结合律失效((a+b)+c ≠ a+(b+c))、误差放大。Kahan 求和用补偿变量追回每步丢掉的低位,是同精度下最经典的自救方案。

第三幕 · 顿悟前兆

融乘阁 · FMA 之光

两次舍入变成一次——精度最坏减半

融乘阁里只有一台法器——三个输入端口 A、B、C,一个输出端口。

诺依曼老祖 诺依曼老祖

「浮点乘法你已经会了,浮点加法你也会了。那 A×B+C 呢?两次运算——两次舍入。如果我只舍入一次呢?」

FMA 对比 · 先乘后加(两次舍入) vs FMA(一次舍入)

切换模式 → 观察 48 位乘积是否截断 → 对比灾难性抵消案例

灾难性抵消案例:A=1.0, B=1+2⁻²⁴, C=−1.0 —
详解 · K11-13 FMA:一次舍入的乘加

普通乘加 A×B+C 有两次舍入:乘积先截回 24 位有效,再加 C 再舍入一次。FMA 把 48 位全精度乘积直接送进宽加法器与 C 相加,全程只舍入一次——最坏误差从 1 ULP 降到 0.5 ULP。在 A=1.0、B=1+2⁻²⁴、C=−1.0 的灾难性抵消里,两次舍入给出 0,FMA 给出真值 2⁻²⁴。现代 GPU/TPU 的矩阵乘单元本质就是海量 FMA 阵列。

第三幕 · 抉择

权衡殿 · 三角之秤

面积、速度、精度——三角上没有免费的午餐

权衡殿中央悬着一架三角天平——三个顶点标着「面积」「速度」「精度」。天平下面散落着你这一路见过的所有模块。

诺依曼老祖 诺依曼老祖

「一颗芯片的面积是有限的。你要在有限面积里装下你的 ALU。每一个模块都要付面积的税。选吧。」

精度之蚀 精度之蚀

「你想要精度?付面积。你想要速度?付面积。你以为精度可以无代价提升?」

三角天平 · 三场景 ALU 配置器

为嵌入式 / 桌面 CPU / AI 加速器各配置 ALU → 观察三角权衡

详解 · K11-14 面积·速度·功耗:不可能三角

每一条加速技巧都在同一张账单上签字:进位选择=加法器翻倍,Wallace 树=布线拥塞,FMA=更宽的加法器与寄存器。嵌入式场景(如 Cortex-M)宁要行波进位换微瓦级功耗;桌面 CPU 用树形乘法器+FMA 换纳秒级延迟;AI 加速器干脆铺满低精度 FMA 阵列换吞吐。架构师的核心手艺不是堆料,而是在给定面积与功耗预算内,把每一平方毫米花在最关键的运算上。

第三幕 · 最高潮

Boss 战 · 精度之蚀

三问清算 · 排除法收网

权衡殿的灯火齐灭。裂缝从四面八方压过来,凝成一个由无数个 0 和 1 溶解又重组的身影——没有面孔,只有一道不断侵蚀的裂痕。它手中悬着一本账簿,封面刻着四个字:精度账本。

精度之蚀 精度之蚀

「你在算经阁里走了十层。每一层,你都在低估我。现在,回答我三个问题。答错,从 S3 重走。但你的刻痕不会消失——它们是你的弹药。」

渊账累计 0 笔 · 三问全对后清零

第三幕 · 收束

尾声 · 十进制之钥

BCD 让 0.1+0.2=0.3——但代价是效率

精度之蚀消散时,算经阁的灯一盏盏亮回来。诺依曼老祖走过来,手里多了一枚铜钥匙——钥匙上刻着十个凹槽,每个凹槽里是一个 0 到 9 的数字。

诺依曼老祖 诺依曼老祖

「银行算利息,0.1 元 + 0.2 元必须等于 0.3 元——不是 0.30000000000000004。一次都不行。怎么办?」

诺依曼老祖 诺依曼老祖

「你这一章设计的加法器、乘法器、FMA 单元——它们每一个都需要好几个周期才能算完。但现代处理器每个时钟周期都在执行指令。怎么做到的?那叫流水线。下一章的题。」

BCD 编码 · 加法修正 · 全章知识图谱

编码 0.1 → BCD → 0.1+0.2=0.3 精确 → 5+8 修正 → 图谱点亮

BCD 加法:5 + 8 = ? —
详解 · K11-15 BCD:十进制的最后堡垒

BCD 用 4 位二进制存一个十进制位(8421 码),0.1 可以精确表示——银行账务、COBOL 系统、计算器至今靠它规避二进制浮点误差。代价:4 位只用 10/16 的编码空间,存储浪费约 20%;加法结果落到 1010–1111 要 +0110 修正(如 5+8=1101→+0110=1 0011,即 13)。IBM z 系列至今保留十进制浮点硬件(IEEE 754-2008 decimal64/128),x86 也有 AAA/DAA 遗留指令。

详解 · K11-10 浮点乘除与全章复盘:指数加、尾数乘

浮点乘法比加法简单:指数相加、尾数相乘(1.xxx×1.yyy 落回 [1,4),至多右规一位)、再舍入;除法则指数相减、尾数相除。乘除没有对阶灾难,但仍要规格化与舍入——以及 SRT 查表那样的实现陷阱。至此本章闭环:整数加减乘除(S1–S3)、浮点表示(S4)、浮点加法(S5)、误差(S6)、FMA(S7)、权衡(S8)、十进制(S10)——一台 ALU 的全部算理精微。

交互实验

ALU 运算

选择运算并拖动 A/B,观察运算结果与零/进位标志。