3. 浮点舍入模式与误差度量

浮点运算的每一步几乎都伴随舍入。IEEE 754 规定了五种舍入方向:就近舍入到偶数(roundTiesToEven)、就近舍入远离零(roundTiesToAway)、向正无穷(roundTowardPositive)、向负无穷(roundTowardNegative)、向零(roundTowardZero)。其中 roundTiesToEven 是默认模式,也是绝大多数硬件与语言运行时的缺省行为;后三种统称定向舍入,常用于区间运算、保守估计和可复现性要求高的场景。

理解舍入模式的关键,是看清“就近”与“定向”在实现上的根本差异。就近舍入先计算无限精度的精确结果,再选择距离最近的可表示浮点数;当精确结果恰好落在两个相邻可表示数的中点时,roundTiesToEven 选择最低有效位为偶数的那个,roundTiesToAway 则选择绝对值更大的那个。定向舍入不关心中点,只按方向取“不小于精确值的最小可表示数”或“不大于精确值的最大可表示数”。这意味着定向舍入的结果误差方向是确定的,而就近舍入的误差方向不确定,但幅度通常更小。

舍入方向与误差度量关系 x a b c 精确值 x 中点 (a+b)/2 就近舍入 → b 向正无穷 → c 向负无穷 → b 向零 → b 误差度量(以 x 与舍入结果 r 为例) 绝对误差 = |r − x|  相对误差 = |r − x| / |x|  ULP = 相邻可表示浮点数的间距 ULP 误差 = |r − x| / ULP(x)  就近舍入的 ULP 误差 ≤ 0.5,定向舍入可接近 1 注:图中 a、b、c 为相邻可表示浮点数,x 位于 b 与 c 之间。

上图中,精确值 x 落在相邻可表示数 b 与 c 之间。就近舍入选择距离更近的 b;向正无穷选择不小于 x 的最小可表示数 c;向负无穷与向零都选择 b。可以看到,定向舍入的结果误差方向固定,而就近舍入的误差方向取决于 x 的位置。当 x 恰好位于中点时,roundTiesToEven 会检查 b 与 c 的最低有效位,选择偶数者,从而避免长期累积的统计偏差。

三种误差度量的工程计算方法

绝对误差是最直观的度量:abs_err = |r - x|,其中 r 是浮点计算结果,x 是精确值或高精度参考值。它的单位与物理量一致,适合判断“结果偏差了多少个基本单位”。但绝对误差无法跨量级比较:同样 1e-6 的绝对误差,对 1e-3 量级的量是灾难,对 1e6 量级的量则微不足道。

相对误差解决了量级问题:rel_err = |r - x| / |x|(x ≠ 0)。工程上常以百分比或科学计数法表示。需要注意,当 x 接近零时相对误差会急剧放大,此时应改用绝对误差或 ULP 度量。相对误差的一个常见陷阱是参考值本身也来自浮点计算,若参考值精度不足,相对误差会被低估,因此工程上应尽量用更高精度类型(如 double 对 float、或任意精度库)计算参考值。

ULP(Unit in the Last Place)是浮点领域最贴近硬件实现的度量。对规格化数,若尾数有 p 位,指数为 e,则 ULP = 2^(e-p+1)。工程上更实用的计算方式是:给定浮点数 x,其 ULP 等于 x 与“下一个可表示浮点数”之间的间距。以 IEEE 754 双精度为例,尾数 52 位显式加 1 位隐含,共 53 位有效位;对 x = 1.0,ULP = 2^-52 ≈ 2.22e-16。ULP 误差定义为 ulp_err = |r - x| / ulp(x),它直接回答“结果偏差了几个最小刻度”。就近舍入的 ULP 误差不超过 0.5,定向舍入的 ULP 误差小于 1,这是判断实现是否正确的有力依据。

实现差异与工程取舍

在 C/C++ 中,可通过 fesetround(FE_TONEAREST / FE_UPWARD / FE_DOWNWARD / FE_TOWARDZERO) 设置舍入模式,并用 #pragma STDC FENV_ACCESS ON 告知编译器不要随意重排浮点运算。Java 的 Math.round 与 BigDecimal.setScale 提供的是十进制舍入语义,与 IEEE 754 二进制舍入不同,混用时必须明确区分。Python 的 decimal 模块支持多种舍入常量,而内置 round 采用银行家舍入(即 roundTiesToEven 的十进制类比)。

定向舍入的典型用途是区间算术:下界用向负无穷,上界用向正无穷,保证真实值始终落在区间内。就近舍入则适合绝大多数数值计算,因为它的误差幅度最小且无系统性偏向。工程上判断一段计算是否可靠,可以同时输出绝对误差、相对误差和 ULP 误差:绝对误差看物理意义,相对误差看跨量级稳定性,ULP 误差看是否贴近硬件极限。三者结合,才能对浮点结果的精度给出完整判断。

实践建议:在编写数值对比测试时,优先使用 ULP 误差作为判据,阈值设为 1~4 ULP;对接近零的量改用绝对误差;对跨量级数据补充相对误差。三种度量同时记录,便于定位误差来源是舍入模式、累积误差还是算法本身。