跳转到主要内容

J.4 浮点乘法

乘法是最简单的浮点运算,因此先讨论它。二进制浮点数表示为 x=s×2ex=s\times2^e。由

(s1×2e1)(s2×2e2)=(s1s2)×2e1+e2 (s_1\times2^{e_1})(s_2\times2^{e_2})=(s_1s_2)\times2^{e_1+e_2}

可知,浮点乘法包含几个部分。首先,用普通整数乘法相乘两个有效数。浮点数以符号与数值分离的形式存储,因此乘法器只需处理无符号数,尽管前面已经看到,Booth 重编码也能方便地处理有符号补码。其次,舍入结果:若有效数是无符号 pp 位数,单精度例如 p=24p=24,乘积最多有 2p2p 位,必须舍入为 pp 位。最后,计算新指数;由于指数带偏置,须从两个偏置指数之和中减去偏置。

例题 以下单精度数的乘法如何在二进制中进行?原页列式为:

1 1000001 0000… = −1 × 2³
0 1000001 1000… =  1 × 2⁴

解答 解包后,两个有效数均为 1.0,乘积也是 1.0,因此结果形式为:

1 ???????? 000…

指数由下式计算:

biased exp⁡(e1+e2)=biased exp⁡(e1)+biased exp⁡(e2)−bias⁡. \operatorname{biased\ exp}(e_1+e_2) =\operatorname{biased\ exp}(e_1)+\operatorname{biased\ exp}(e_2)-\operatorname{bias}.

图 J.7 给出的偏置为 127=011111112127=01111111_2,因此 −127 的补码为 10000001210000001_2。乘积的偏置指数为

  10000010
  10000011
+ 10000001
----------
  10000110

这是十进制 134,对应实际指数 134−127=7134-127=7,与预期一致。

译注:原页例题把两个 8 位指数的末位排进了后面的字段,显示成 7 位指数。按题中数值及解答,完整编码应分别为 1 10000010 000… 与 0 10000011 000…。

浮点乘法最值得关注的是舍入。图 J.9 展示几种情况;各进制的处理类似,因此采用更便于阅读的十进制。

情况精确乘法舍入判断三位有效数字结果
(a)1.23×6.78=8.33941.23\times6.78=8.3394第四位 r=9>5r=9>5,进一8.34
(b)2.83×4.47=12.65012.83\times4.47=12.6501r=5r=5,后面存在非零位,进一1.27×1011.27\times10^1
(c)1.28×7.81=09.99681.28\times7.81=09.9968r=6>5r=6>5,进一并产生进位1.00×1011.00\times10^1

图 J.9 乘法舍入示例,十进制且 p=3p=3。(a)、(b) 表明乘积可以有 2p−12p-1 或 2p2p 位,因此舍入时加 1 的位置可能不同;(c) 表明舍入进一可能产生向更高位的进位。

这里 p=3p=3,所以最终保留最高的三位有效数字,第四位称为舍入位,记作 rr。若 r<5r<5,直接保留前三位;若 r>5r>5,如 (a),就在第三位加 1;若 r=5r=5,如 (b),还要检查后面的数字,决定直接截断还是加 1。其实只需知道后面是否存在非零位,可以用 sticky 位记录。比较 (a)、(b) 可知,相对于乘积最低位,舍入位可能位于两个不同位置。(c) 说明,加 1 可能产生进位,此时有效数会先变为 10.0。

用图 J.2 的乘法器再加一个 sticky 位,就能直接实现舍入。设 A、B、P 都为 pp 位。相乘得到 (P,A) 中的 2p2p 位乘积。乘法过程中,前 p−2p-2 次移入 A 的位都与 sticky 位作 OR。令 ss 表示 sticky 位,gg(guard,保护位)为 A 的最高位,rr 为 A 的次高位,则有两种情况:

  1. P 的最高位为 0:将 P 左移一位,把 A 的 gg 位移入;A 的其他位无需移动。
  2. P 的最高位为 1:令 s:=s∨rs:=s\lor r、r:=gr:=g,并把指数加 1。

现在,若 r=0r=0,P 就是正确舍入的乘积;若 r=1,s=1r=1,s=1,应取 P+1,这里的加 1 指在 P 的最低位加 1;若 r=1,s=0r=1,s=0,恰好落在中点,是否进一取决于 P 的最低位。

浮点乘法中两种规格化与舍入情况
图 J.10 浮点乘法算法的两种情况。顶行是有效数相乘后 P、A 的内容,p=6。情况 (1) 首位为 0,需移动 P;情况 (2) 首位为 1,无需移位,但必须调整指数、舍入位和 sticky 位。sticky 位是所有标为 s 的位的逻辑 OR。

图中 Product 为乘积;Case (1): Shift needed 表示情况 (1) 需要移位;Case (2): Increment exponent 表示情况 (2) 指数加 1。调整二进制小数点位置时,须给指数加 1 来补偿。rnd 表示舍入位,sticky 表示黏滞信息。

把相同规则用于图 J.9(b) 的十进制例子:乘法后 P=126、A=501,g=5,r=0,s=1g=5,r=0,s=1。P 的最高位非零,属于情况 (2),令 r:=gr:=g,得到 r=5r=5。这似乎可能是中点,但 s=1s=1 表明实际略大于半个末位单位,所以向 P 加 1,得到正确舍入结果。

精确规则取决于舍入模式,见图 J.11。注意,P 非负,保存的是结果的绝对值。更高效的实现可参考 Santoro、Bewick 与 Horowitz [1989]。

舍入模式结果非负时加 1 的条件结果为负时加 1 的条件
向 −∞-\inftyr∨sr\lor s
向 +∞+\inftyr∨sr\lor s
向 0
舍入到最近值(r∧p0)∨(r∧s)(r\land p_0)\lor(r\land s)(r∧p0)∨(r∧s)(r\land p_0)\lor(r\land s)

图 J.11 IEEE 舍入模式的实现规则。令 S 为初步结果的绝对值。空白表示直接取 S 最高的 p 位;若表中条件成立,就向第 p 个最高有效位加 1。r、s 分别为舍入位和 sticky 位,p0p_0 为 S 的第 p 个最高有效位。

例题 在二进制、p=4p=4 时,说明算法如何在四种舍入模式下计算 −5×10-5\times10。

解答 −5=−1.0102×22-5=-1.010_2\times2^2,10=1.0102×2310=1.010_2\times2^3。有效数的整数乘积为 01100100201100100_2,所以 P=0110、A=0100,g=0,r=1,s=0g=0,r=1,s=0。P 的最高位为 0,按情况 (1) 左移,变为 1100。结果为负,依图 J.11:

模式有效数各位原因
向 −∞-\infty110121101_2r∨s=1∨0r\lor s=1\lor0 为真,加 1
向 +∞+\infty110021100_2不加 1
向 0110021100_2不加 1
最近值110021100_2r∧p0=1∧0r\land p_0=1\land0 与 r∧s=1∧0r\land s=1\land0 均为假

指数为 2+3=52+3=5,所以除向 −∞-\infty 舍入得到 −1.1012×25=−52-1.101_2\times2^5=-52 外,其余模式均得到 −1.1002×25=−48-1.100_2\times2^5=-48。

舍入结果大到无法表示时,就发生上溢。单精度中,结果指数不小于 128 就会如此。若 e1,e2e_1,e_2 为偏置指数,则 1≤ei≤2541\le e_i\le254,因此 e1+e2−127e_1+e_2-127 的范围为 1+1−1271+1-127 到 254+254−127254+254-127,即 −125 到 381。

原书指出,这个范围可用 9 位表示,因此一种检测办法是在 9 位加法器中计算指数,见习题 J.12。一定要在舍入之后检查上溢,图 J.9(c) 已说明舍入可能改变这一判断。

译注:9 位只有 512 种编码,而上述区间有 507 个值,但不能直接套用普通 9 位有符号补码范围 −256…255,或把负数当普通无符号数。这里的 9 位指数运算须配合适当编码与进位判别;原书把具体检测留给习题 J.12。

非规格化数

由于非规格化数的存在,下溢检查更复杂。单精度结果指数小于 −126,不一定就意味着下溢,因为结果仍可能是可表示的非规格化数。例如,(1×2−64)(1×2−65)=1×2−129=0.125×2−126(1\times2^{-64})(1\times2^{-65})=1\times2^{-129}=0.125\times2^{-126}。通常,乘积的无偏置指数低于 −126 时,必须右移乘积并递增指数,直到指数达到 −126。若整个有效数都被移出,就发生下溢。下溢的精确定义更微妙,见 J.7 节。

若一个乘法操作数是非规格化数,有效数带前导 0,乘积也会带前导 0。若乘积指数小于 −126,结果是非规格化数,仍按上法右移并递增指数。若指数大于 −126,结果可能成为规格化数;此时左移乘积并递减指数,直到规格化,或指数降至 −126。

非规格化数是浮点乘法实现中的一个主要障碍,因为它要求乘法器支持本来不需要的可变位数移位。因此,高性能浮点乘法器常常不直接处理非规格化数,而是触发陷阱交由软件处理。有些实际程序即便正常运行也会频繁下溢,它们在需要陷阱处理非规格化数的系统中会慢很多。

还没有讨论操作数为 0 的情况。可以在开始乘法前检查两个操作数,也可以在乘法后检查乘积。如果事后检查,要正确处理 0×∞0\times\infty:结果是 NaN,而不是 0。确认结果为 0 后,把偏置指数设为 0。不要忘记符号:即使结果为 0,乘积符号也是两个操作数符号的 XOR。

乘法的精度

整数乘法需要决定返回乘积的低位字还是完整乘积;浮点乘法也有类似问题:精确乘积应舍入到操作数精度,还是高一级精度?标准高级语言中,整数乘法没有可生成“单精度乘单精度得双精度”指令的结构。

浮点情况则不同。很多语言允许把两个单精度变量的乘积赋给双精度变量,数值算法也能利用这种运算。最著名的例子,是用迭代改进法求解线性方程组。