跳转到主要内容

J.7 浮点算术的其他问题

结束浮点算术讨论之前,再介绍几个主题。

融合乘加

浮点单元最常见的用途可能是矩阵运算,而最频繁的矩阵运算是矩阵与矩阵或向量相乘,归根结底需要计算内积 x1y1+x2y2+⋯+xnynx_1y_1+x_2y_2+\cdots+x_ny_n,也就是一连串乘加组合。

为此,IBM RS/6000 引入了用一条指令计算 ab+cab+c 的融合乘加(fused multiply-add)。虽然一条指令需要读取三个操作数,它仍有望提高内积性能。

融合乘加先精确计算 ab+cab+c,最后只舍入一次。少一次舍入确实能略微提高内积精度,但这不是主要动机。它有两个主要优势:一是舍入可能需要加法,实现代价高,只舍入一次就消去了一次加法。

二是它增加的准确性,可用于在硬件未直接提供时实现正确舍入的除法和平方根,也可构建高效的多精度浮点软件包。

用融合乘加实现正确舍入除法有很多细节,但基本思想简单。再看 J.6 节的 a=13,b=51a=13,b=51:两位精度下,1/b1/b 舍入为 b′=.020b'=.020,ab′ab' 舍入为 q′=.26q'=.26,并非正确商。应用两次融合乘加即可调整:

r=a−bq′,q′′=q′+rb′. r=a-bq',\qquad q''=q'+rb'.

普通两位运算中,bq′=51×.26bq'=51\times.26 舍入为 13,因此 r=0r=0,无法调整。但融合乘加给出 r=13−(51×.26)=−.26r=13-(51\times.26)=-.26,再得 q′′=.26−.0052=.2548q''=.26-.0052=.2548,正确舍入为 .25。细节见 Montoye、Hokenek 与 Runyon [1990]、Markstein [1990]。

精度

本附录讨论的标准规定四种精度:单精度、扩展单精度、双精度、扩展双精度,参数见图 J.7。实现不必全部提供,但鼓励支持“单精度与扩展单精度”,或“单精度、双精度与扩展双精度”两种组合之一。科学计算广泛使用双精度,实际几乎总会实现它。因此,设计者通常只需决定是否提供扩展双精度,以及提供多少位。

Motorola 68882 与 Intel 387 协处理器采用标准允许的最小扩展格式:80 位,其中有效数 64 位。但许多较新的高性能浮点芯片没有实现 80 位扩展精度,原因之一是它与 64 位总线、寄存器配合不便。SPARC V8、PA-RISC 等新体系结构规定 128 位扩展精度,或称四倍精度(quad),并形成 15 位指数、113 位有效数的事实约定。

虽然多数高级语言不允许直接使用扩展精度,它对数学软件作者很有价值。例如,编写计算平面向量 (x,y)(x,y) 长度 x2+y2\sqrt{x^2+y^2} 的库函数,若 x>2Emax⁡/2x>2^{E_{\max}/2},直接计算就会溢出。这意味着要么把函数允许的指数范围减半,要么使用缩放等更复杂的算法。

有扩展精度时,简单算法就能工作。向量长度本身不复杂,也不难设计避免溢出的算法;但更复杂的问题中,扩展精度可能决定能否采用简单快速的算法。二进制到十进制转换就是好例子。Coonen [1984] 清楚介绍了一种依赖扩展精度的高效算法,Goldberg [1991] 也作了简述。准确计算超越函数同样会因扩展精度而容易许多。

精度还有一个重要问题:两次舍入(double rounding)。假设十进制单精度为两位、扩展精度为三位,计算 1.9×.661.9\times.66。精确积为 1.254,先舍入到扩展精度得 1.25,再舍入到单精度得 1.2;但直接舍入到单精度应为 1.3。因此,两次舍入未必等于一次。

若硬件只做双精度,能否先算双精度再舍入为单精度来模拟单精度?上述例子似乎说不能,但两次舍入并不总有危险。下列结论成立,证明不容易,见习题 J.25:

译注:这里的习题编号沿用原文;本附录实际讨论两次舍入的是习题 J.24,习题 J.25 讨论浮点异常的快速检测。

若 x、y 的有效数为 p 位,将 x+y 精确计算后先舍入至 q 位,只要 q≥2p+2q\ge2p+2,再舍入至 p 位就不会改变本应得到的答案。这不只适用于加法,也适用于乘法、除法和平方根。

上面的十进制例子 q=3,p=2q=3,p=2,不满足条件。IEEE 二进制中,双精度 q=53q=53、单精度 p=24p=24,有 53≥2×24+2=5053\ge2\times24+2=50。因此,可先正确计算双精度结果,即精确运算后舍入到双精度,再舍入到单精度,以实现单精度运算。

译注:原页正文重复这个不等式时,关系符被排为异常字符;此处依据同页完整命题恢复为 ≥\ge。本段讨论须连同标准的舍入及指数范围条件理解;后文还会专门说明下溢时的两次舍入问题。

异常

IEEE 标准定义五类异常:下溢、上溢、除以零、不精确(inexact)和无效操作(invalid)。默认只置标志,计算继续。标志具有 sticky 性质,一旦置位,直到显式清除才复位。标准强烈建议为每类异常提供陷阱使能位。若已使能的异常发生,则调用用户陷阱处理程序,相关异常标志值未定义。J.3 节的 −3=NaN\sqrt{-3}=\mathrm{NaN}、1/0=∞1/0=\infty 就是异常:默认前者置无效操作标志并返回 NaN,后者置除以零标志并返回无穷。

多数其他系统也有下溢、上溢与除以零异常。无效操作针对 −1\sqrt{-1}、0/00/0、∞−∞\infty-\infty 等无法自然赋予普通浮点值或 ±∞\pm\infty 的运算。不精确异常是 IEEE 算术的特色:结果需要舍入或发生上溢时触发。由于除以零和上溢都可能返回无穷,必须查看异常标志才能区分。不精确异常出现得太频繁,几乎谈不上“异常”,因此为它使能陷阱往往严重影响性能。除下溢外,是否使能陷阱不会改变一个运算是否算异常,后面再谈下溢。

标准假定,陷阱发生后可以识别触发它的运算及操作数。流水线或多个算术单元的机器中,仅检查程序计数器可能不够,可能需要硬件支持以准确定位。

还有如下问题:

r1 = r2 / r3
r2 = r4 + r5

两条指令可能并行执行。若除法触发陷阱,它的操作数 r2 可能已经被加法覆盖,尤其因为加法通常比除法快得多。支持 IEEE 陷阱的系统必须设法保存原 r2,或者由硬件保存,或者由编译器避免这种情形。这并非浮点独有,例如:

r1 = 0(r2)
r2 = r3

等待内存时执行第二条指令很高效;但若访问 0(r2) 发生缺页,重启第一条指令所需的 r2 可能已经丢失。

MIPS R3010 的办法,是在指令周期早期识别可能异常的指令。例如,浮点加法只有当至少一个操作数指数为 Emax⁡E_{\max} 时才可能上溢。这个早期检查是保守的,可能把实际不异常的操作也标出来;但只要这种误报稀少,性能就很好。被标记为可能异常的指令,由陷阱处理程序中的专门代码计算,避免破坏状态。记住,这些问题只在使能陷阱时存在;否则,正常执行中设置异常标志很直接。

下溢

前面多次提到,下溢检测比其他异常复杂。IEEE 标准规定,使能下溢陷阱时,若结果是非规格化数,就必须触发陷阱。

若未使能,则只有损失准确性、结果必须舍入时,才置下溢标志。理由是:若下溢并未损害准确性,就没必要报警;但若用户使能陷阱,可能是想模拟直接置零,因此结果一旦低于 1.0×2Emin⁡1.0\times2^{E_{\min}} 就应通知。

所以,无陷阱时,只有结果既非规格化又不精确,才报告下溢。不过,“非规格化”与“不精确”都有不同解释。通常,不精确意味着真实结果不能准确表示、必须舍入。设二进制有效数为三位,采用舍入到最近值,计算

(1.112×2−2)(1.112×2Emin⁡)=0.1100012×2Emin⁡. (1.11_2\times2^{-2})(1.11_2\times2^{E_{\min}}) =0.110001_2\times2^{E_{\min}}.

返回 0.112×2Emin⁡0.11_2\times2^{E_{\min}},发生了舍入,应报告不精确。但是否还要报告下溢?渐进下溢损失有效位,是由于指数范围受限。若指数范围无限,结果将是 1.102×2Emin⁡−11.10_2\times2^{E_{\min}-1},与渐进下溢的结果完全相同;非规格化有效数位数较少,在本例并未造成额外差别。

标准说明文档 [Cody 等 1984] 鼓励以此为判据:只有实际结果与“相同小数位数、但指数范围足够大”的系统所得结果不同时,才置下溢标志。不过,这种实现较难,因此标准也允许只要结果非规格化且不同于无限精度结果,就置下溢标志。

对于“结果为非规格化”也有两种定义。原书例子是 1.102×2−11.10_2\times2^{-1} 乘以 1.102×2Emin⁡1.10_2\times2^{E_{\min}},原文把精确积写为 0.11112×2Emin⁡0.1111_2\times2^{E_{\min}},舍入后为规格化数 1.002×2Emin⁡1.00_2\times2^{E_{\min}}。若此时报告下溢,就是采用舍入前规则,因为舍入前非规格化;若不报告,就是采用舍入后规则。标准允许任选其一,但所有运算必须保持一致。

译注:原页本例的操作数与声称的乘积不一致。所列 1.102×1.102×2−11.10_2\times1.10_2\times2^{-1} 实为 1.00121.001_2,不是 0.111120.1111_2。本段保留原例,并仅据其声称的 0.111120.1111_2 结果解释两种检测时机;不可把原列乘法当作正确演算。

以浮点加法说明这些规则:加减法结果若为非规格化数,它总是精确的,因此不必设置下溢标志。未使能陷阱时,没有异常;使能时,下溢标志值未定义,也无需设置。

最后还有一个细节。未使能陷阱时,pp 位数运算的下溢结果是精度不超过 p−1p-1 位的非规格化数;使能时,传给处理程序的却是舍入至 pp 位、指数回绕后的结果。

这就可能出现两次舍入问题。处理程序若想返回非规格化结果,不能只对收到的参数再舍入,否则可能出错。因此,至少还须传递一位额外信息,才有可能正确舍入。