Day 62 · 2026.08.23

数值分析与浮点世界

计算机里没有实数——只有一套刻度会伸缩的替代品,以及一门为它记账的学问
「即使舍入误差全部消失,数值分析仍会剩下 95%。」 —— Nick Trefethen

浮点数与机器精度

Floating Point & Machine Epsilon · 在对数尺度上撒点
Representation
直觉版

实数轴连续,64 位只装得下约 $1.8\times10^{19}$ 个数。问题不是装多少,而是撒在哪。定点数均匀撒点,等于让量原子与量星系共用一把毫米刻度。浮点在对数尺度上均匀撒点:每个二进制量级 $[2^e,2^{e+1})$ 内点数相同,刻度随数值伸缩,于是绝对精度一直在变,相对精度恒定

$0.1+0.2\neq0.3$ 由此而来:$1/10$ 的分母含质因子 5,二进制里必然无限循环,正如 $1/3$ 在十进制写不完。不是精度不够,是十进制的 0.1 在二进制世界里不存在。

浮点:每量级点数相同 → 间距随数值加倍 1 2 4 8 16 定点:间距恒定 → 小数处浪费,大数处不够 同样 25 个点:浮点买相对精度,定点买绝对精度
正式定义
$$\mathrm{fl}(x)=x\,(1+\delta),\qquad |\delta|\le u=2^{-53}\approx1.11\times10^{-16}$$

$\mathrm{fl}$ 是「舍入到最近可表示数」,$\delta$ 是它引入的相对误差,$u$ 称 unit roundoff。整式只说一句话:舍入相当于把输入换成一个相差不到 $u$ 的邻居。双精度写作 $\pm(1.b_1\cdots b_{52})_2\times2^{e}$——53 位有效数字(首位的 1 不占存储),11 位指数覆盖 $10^{\pm308}$。

为什么美

一个 64 位模式覆盖 600 多个数量级,而任何一处的相对误差都相同——浮点的均匀性建在乘法群上,不在加法群上。推论很锋利:乘除几乎无损(指数相加、尾数相乘,各吃一次舍入),加减才是危险的那一半——浮点数是为乘法设计的数系。边界处同样优雅:次正规数、$\pm0$、$\infty$、NaN 让运算对所有输入封闭,Kahan 的哲学是让异常成为一个数,而不是一次崩溃

应用

AI 硬件的每次精度取舍都在读这张表。fp16 是 5 位指数 + 10 位尾数;bf16 保留 fp32 完整的 8 位指数,只把尾数砍到 7 位——训练怕梯度溢出成 $\infty$,不怕小数点后少几位。同样 16 位,换个切法就换一种失败模式。反向的例子是金融:那里用十进制定点,因为 0.1 元必须精确等于一角。

一句话精华 + 思考题
浮点数不是实数的近似,而是刻度随数值伸缩的独立数系——它承诺相对精度,从不承诺绝对精度。
思考:若你的量在一次计算里跨越 20 个数量级,浮点是帮了你,还是替你把问题藏了起来?

条件数

Condition Number · 问题的病,不是算法的错
Conditioning
直觉版

把「算不准」拆成两个独立问题:问题本身对输入扰动敏不敏感?算法有没有额外制造误差? 条件数只量前者,与语言、硬件、算法无关。

解 $Ax=b$ 就是求两条直线的交点:夹角大时挪一点线、交点也只挪一点;近乎平行时同样的挪动会把交点甩出很远——$\kappa(A)$ 量的正是「有多平行」。$\kappa=10^{8}$ 意味着双精度 16 位有效数字进去、8 位出来;这 8 位不是算法弄丢的,是问题在信息层面就没传过去。

两条近乎平行的直线 → 交点极不稳定 交点跑掉一大截 虚线 = 第二条线挪动一丝(数据的微小扰动) κ(A) = 输出相对变化 ÷ 输入相对变化 的最坏比值
正式定义
$$\frac{\|\delta x\|}{\|x\|}\;\le\;\kappa(A)\,\frac{\|\delta b\|}{\|b\|},\qquad \kappa(A)=\|A\|\,\|A^{-1}\|=\frac{\sigma_{\max}}{\sigma_{\min}}$$

左边是解的相对变化,右边是数据的相对变化,$\kappa$ 是两者间的最坏放大率。$\sigma$ 是奇异值:$A$ 把单位球拉成椭球,$\sigma_{\max}$ 与 $\sigma_{\min}$ 是最长轴与最短轴,所以条件数 = 椭球有多扁。经验法则:一次求解约损失 $\log_{10}\kappa$ 位有效数字。

为什么美

它把「数值困难」从工程抱怨升级成问题的内蕴不变量,并把四件看似无关的事认成同一件:病态方程组;多项式根对系数的敏感(Wilkinson 那个 20 次多项式,一个系数动 $2^{-23}$,整数根就飞上复平面);混沌的 Lyapunov 指数;深度网络的梯度爆炸与消失,那是逐层雅可比条件数的连乘。同一个量,四个学科各给了它一个名字。

应用

知道 $\kappa$ 后,一大批「数值技巧」显出同一个动机:压小它。岭回归的 $\lambda I$ 抬高 $\sigma_{\min}$;共轭梯度的迭代次数正比于 $\sqrt{\kappa}$,预条件子 $M^{-1}A$ 的意义就是换一个条件数更小的等价问题;谱归一化与正交初始化把雅可比的谱压向 1;Adam 则是对角预条件子,按坐标缩放梯度,正是在把椭球拉圆。

一句话精华 + 思考题
条件数是问题自带的信息衰减率:它先决定你最多能算准几位,剩下的才轮到算法负责。
思考:上次把学习率降到「终于能收敛」,是修好了优化,还是只绕开了一个 $\kappa$ 太大的参数化?

灾难性抵消

Catastrophic Cancellation · 减法不制造误差,它揭露误差
Cancellation
直觉版

两个接近的数相减:$1.2345678-1.2345600=0.0000078$。输入各有 8 位有效数字,结果只剩 2 位——而减法一位都没算错,它只把原本藏在末位的不确定性提拔成了首位。噪声一直都在,此前被高位挡着。乘除的相对误差只逐次累加,唯有相近数相减能把它放大任意倍。这是浮点世界唯一称得上「灾难性」的一步。

课本的二次求根公式就踩在这坑上:当 $b>0$ 且 $4ac\ll b^2$,$x=\frac{-b+\sqrt{b^2-4ac}}{2a}$ 的分子成了两个近乎相等的正数相减。改法只需一行:先算稳的那根 $x_1=\frac{-b-\sqrt{b^2-4ac}}{2a}$,另一根用韦达定理 $x_2=c/(ax_1)$ 反推。同一个恒等式,两条路径,数值上天差地别。

a b a−b 前 14 位完全抵消,归零 只剩 2 位,其中 1 位本就是舍入噪声 红格 = 舍入噪声 蓝格 = 可信有效位 噪声没有变大,只是从末位被提拔成首位
正式定义
$$\frac{\big|(\hat a-\hat b)-(a-b)\big|}{|a-b|}\;\le\;\frac{|a|+|b|}{|a-b|}\cdot u$$

$\hat a,\hat b$ 是已带舍入误差的存储值,$u$ 是机器精度。右边的因子 $\frac{|a|+|b|}{|a-b|}$ 就是放大倍数,$a\to b$ 时趋于无穷。它正是减法这个问题的条件数——抵消不是新现象,是条件数在最简单的一次运算上现了原形。

为什么美

它戳破一个默认为真的假设:实数域上成立的恒等式,在浮点上不是同一个程序。$a^2-b^2$ 与 $(a-b)(a+b)$ 是同一个数却有两种命运——抽象的等价被有限字长撕开一道缝,整门数值分析从缝里长出来。补救更漂亮:Kahan 求和用一个补偿变量追回每次加法舍掉的低位,把误差界从 $O(nu)$ 压到与 $n$ 无关。那些「丢掉」的位从未离开寄存器,只要你肯弯腰去捡。

应用

方差的「一遍公式」$E[X^2]-(E[X])^2$ 在均值远大于标准差时会算出负数,Welford 在线算法就是为它而写;expm1log1p 存在的唯一理由,是 $x$ 极小时 $e^x-1$ 与 $\log(1+x)$ 会把有效位抵消干净;GPU 上 all-reduce 求和顺序不定,这正是大模型训练难以逐位复现的根因之一。

一句话精华 + 思考题
相近数相减不产生误差,它只是撤走了掩体——你的有效数字早就在别处被吃掉了。
思考:你的代码里有没有一处 $A-B$,而 $A$ 与 $B$ 各自都是很长的累加和?

后向误差与低精度

Backward Error & Low Precision · 把误差推回输入端
Stability
直觉版

Wilkinson 在 1960 年代翻转了提问方式。传统问「答案离真答案多远」(前向误差)——很难答,因为它把问题的病与算法的错混在一起。Wilkinson 改问:「我算出的这个答案,是哪个问题的精确解?」

若 $\hat x$ 恰好精确满足 $(A+\Delta A)\hat x=b$ 而 $\|\Delta A\|/\|A\|$ 只有 $10^{-15}$,算法就无可指摘:它交出的是一个邻近问题的精确解,而 $A$ 本就来自测量,本就带着大得多的不确定性。好算法的定义于是变成:给出邻近问题的精确解。

数据 A, b 真解 x 邻近数据 A+ΔA 算出的解 x̂ 精确求解 精确求解 后向误差 η 前向误差 浮点算法 前向误差 ≤ 条件数 × 后向误差 Wilkinson 图:算出的解是「邻近问题」的精确解
正式定义
$$\underbrace{\frac{\|\hat x-x\|}{\|x\|}}_{\text{前向误差}}\;\lesssim\;\underbrace{\kappa(A)}_{\text{问题的病}}\;\times\;\underbrace{\eta}_{\text{后向误差}}$$

这是数值分析的基本方程,把责任劈成两半:$\kappa$ 不可修(只能换问题的提法),$\eta$ 可修——那才是算法设计的战场。带部分主元的高斯消元满足 $\eta\le c_n\rho u$,$\rho$ 是消元中元素的增长因子,选主元就是为压住它:最坏界仍是 $2^{n-1}$,实践中却几乎总在 $O(n)$ 附近——这个差距至今没有令人满意的解释。

为什么美

框架自动适应精度:把 $u$ 从 $2^{-53}$ 换成 bf16 的 $2^{-8}$,整套不等式一字不改。于是「该用几位」从玄学变成一次算术——混合精度迭代精化就是答案:低精度做 $O(n^3)$ 的分解,高精度算 $O(n^2)$ 的残差并回代修正,几轮下来赚回双精度的准确度。用便宜的算力生产答案,用昂贵的算力做审计。

应用

LAPACK 与 cuSOLVER 的每个例程都标注自己的后向稳定性——这不是学术礼节,是接口契约。深度学习的混合精度训练是同一本账:fp16 前向反向、fp32 主权重副本、loss scaling 把梯度上移以避开 fp16 的次正规区。LLM 的 int8/int4 量化也换了问法:不问「掉了多少精度」,而问「量化后的模型是哪个权重略有不同的模型的精确执行」——关键于是变成扰动落在哪个方向,GPTQ 与 AWQ 的核心正是把误差推进激活不敏感的子空间。

一句话精华 + 思考题
与其追问答案错了多远,不如追问它精确回答了哪个问题——把误差推回输入端,才分得清是问题病了还是算法错了。
思考:模型量化后只掉 0.3% 精度,是量化算法好,还是这个模型本就足够「良态」?

深入思考

浮点误差完全确定,为什么同一份代码在 GPU 上两次跑出不同结果?
根源不是随机性,是浮点加法不满足结合律:$(a+b)+c$ 与 $a+(b+c)$ 可以差在末位。于是「求和」在浮点上不是良定义的运算,它依赖一棵具体的求和树,而多线程归约、atomicAdd、按尺寸自动选算法都会改变这棵树。要逐位复现就得固定归约顺序,代价是吞吐。深层含义是:我们默认「数学上相等就是同一次计算」,浮点里却算法即语义
低精度训练居然 work,是不是说明神经网络本身就「良态」?
更准确的说法是:SGD 自带的梯度噪声远大于量化噪声——梯度本就是小批量的随机估计,再叠一层舍入扰动不改变其统计性质。但「良态」是分层的:前向传播良态,长期累加却病态——优化器状态与权重更新正是「大数吸收小增量」的抵消场景,所以必须保留 fp32 主权重副本。
区间算术或精确有理运算,能不能一劳永逸消灭浮点误差?
能给出严格结论,代价却通常不可接受:区间算术保证真值落在区间内,可宽度随迭代指数增长,常宽到毫无信息;精确有理数的分子分母位数会爆炸。真正的答案是——误差不是要消灭的,是要记账的。接受它并证明它的界,远比消灭它可行。
条件数说「损失 $\log_{10}\kappa$ 位数字」——计算中的「信息」能像香农熵一样度量吗?
不止是类比。有效数字位数就是比特数,$\log_2\kappa$ 就是一次求解的比特损失量:病态线性系统是一条有损信道,$\kappa$ 是衰减率(呼应 Day 13)。区别在机制——信道是随机噪声,这里是确定性塌陷:$A$ 把某些方向压到 $\sigma_{\min}$ 附近,那些方向的信息在有限字长下被截断。判据随之而来:若 $\kappa>10^{16}$,再高的精度也只是把损失往后推一位,该做的是换一个提法