实数轴连续,64 位只装得下约 $1.8\times10^{19}$ 个数。问题不是装多少,而是撒在哪。定点数均匀撒点,等于让量原子与量星系共用一把毫米刻度。浮点在对数尺度上均匀撒点:每个二进制量级 $[2^e,2^{e+1})$ 内点数相同,刻度随数值伸缩,于是绝对精度一直在变,相对精度恒定。
$0.1+0.2\neq0.3$ 由此而来:$1/10$ 的分母含质因子 5,二进制里必然无限循环,正如 $1/3$ 在十进制写不完。不是精度不够,是十进制的 0.1 在二进制世界里不存在。
$\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 元必须精确等于一角。
把「算不准」拆成两个独立问题:问题本身对输入扰动敏不敏感? 和 算法有没有额外制造误差? 条件数只量前者,与语言、硬件、算法无关。
解 $Ax=b$ 就是求两条直线的交点:夹角大时挪一点线、交点也只挪一点;近乎平行时同样的挪动会把交点甩出很远——$\kappa(A)$ 量的正是「有多平行」。$\kappa=10^{8}$ 意味着双精度 16 位有效数字进去、8 位出来;这 8 位不是算法弄丢的,是问题在信息层面就没传过去。
左边是解的相对变化,右边是数据的相对变化,$\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 则是对角预条件子,按坐标缩放梯度,正是在把椭球拉圆。
两个接近的数相减:$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)$ 反推。同一个恒等式,两条路径,数值上天差地别。
$\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 在线算法就是为它而写;expm1 与 log1p 存在的唯一理由,是 $x$ 极小时 $e^x-1$ 与 $\log(1+x)$ 会把有效位抵消干净;GPU 上 all-reduce 求和顺序不定,这正是大模型训练难以逐位复现的根因之一。
Wilkinson 在 1960 年代翻转了提问方式。传统问「答案离真答案多远」(前向误差)——很难答,因为它把问题的病与算法的错混在一起。Wilkinson 改问:「我算出的这个答案,是哪个问题的精确解?」
若 $\hat x$ 恰好精确满足 $(A+\Delta A)\hat x=b$ 而 $\|\Delta A\|/\|A\|$ 只有 $10^{-15}$,算法就无可指摘:它交出的是一个邻近问题的精确解,而 $A$ 本就来自测量,本就带着大得多的不确定性。好算法的定义于是变成:给出邻近问题的精确解。
这是数值分析的基本方程,把责任劈成两半:$\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 的核心正是把误差推进激活不敏感的子空间。