# J.4 浮点乘法
<!-- source: p1240 -->

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

$$
(s_1\times2^{e_1})(s_2\times2^{e_2})=(s_1s_2)\times2^{e_1+e_2}
$$

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

<!-- source: p1241 -->

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

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

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

```text
1 ???????? 000…
```

指数由下式计算：

$$
\operatorname{biased\ exp}(e_1+e_2)
=\operatorname{biased\ exp}(e_1)+\operatorname{biased\ exp}(e_2)-\operatorname{bias}.
$$

图 J.7 给出的偏置为 \(127=01111111_2\)，因此 −127 的补码为 \(10000001_2\)。乘积的偏置指数为

```text
  10000010
  10000011
+ 10000001
----------
  10000110
```

这是十进制 134，对应实际指数 \(134-127=7\)，与预期一致。

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

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

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

图 J.9　乘法舍入示例，十进制且 \(p=3\)。(a)、(b) 表明乘积可以有 \(2p-1\) 或 \(2p\) 位，因此舍入时加 1 的位置可能不同；(c) 表明舍入进一可能产生向更高位的进位。
{#fig-J.9 .wide .expand num="J.9"}

<!-- source: p1242 -->

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

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

1. P 的最高位为 0：将 P 左移一位，把 A 的 \(g\) 位移入；A 的其他位无需移动。
2. P 的最高位为 1：令 \(s:=s\lor r\)、\(r:=g\)，并把指数加 1。

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

![浮点乘法中两种规格化与舍入情况](/figures/app-j/fig-j-10.png)
{#fig-J.10 .wide .expand num="J.10" caption="浮点乘法算法的两种情况。顶行是有效数相乘后 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 表示黏滞信息。

<!-- source: p1243 -->

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

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

| 舍入模式 | 结果非负时加 1 的条件 | 结果为负时加 1 的条件 |
|---|---|---|
| 向 \(-\infty\) |  | \(r\lor s\) |
| 向 \(+\infty\) | \(r\lor s\) |  |
| 向 0 |  |  |
| 舍入到最近值 | \((r\land p_0)\lor(r\land s)\) | \((r\land p_0)\lor(r\land s)\) |

图 J.11　IEEE 舍入模式的实现规则。令 S 为初步结果的绝对值。空白表示直接取 S 最高的 p 位；若表中条件成立，就向第 p 个最高有效位加 1。r、s 分别为舍入位和 sticky 位，\(p_0\) 为 S 的第 p 个最高有效位。
{#fig-J.11 .wide .expand num="J.11"}

**例题**　在二进制、\(p=4\) 时，说明算法如何在四种舍入模式下计算 \(-5\times10\)。

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

| 模式 | 有效数各位 | 原因 |
|---|---|---|
| 向 \(-\infty\) | \(1101_2\) | \(r\lor s=1\lor0\) 为真，加 1 |
| 向 \(+\infty\) | \(1100_2\) | 不加 1 |
| 向 0 | \(1100_2\) | 不加 1 |
| 最近值 | \(1100_2\) | \(r\land p_0=1\land0\) 与 \(r\land s=1\land0\) 均为假 |

指数为 \(2+3=5\)，所以除向 \(-\infty\) 舍入得到 \(-1.101_2\times2^5=-52\) 外，其余模式均得到 \(-1.100_2\times2^5=-48\)。

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

<!-- source: p1244 -->

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

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

## 非规格化数

由于非规格化数的存在，下溢检查更复杂。单精度结果指数小于 −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\times\infty\)：结果是 NaN，而不是 0。确认结果为 0 后，把偏置指数设为 0。不要忘记符号：即使结果为 0，乘积符号也是两个操作数符号的 XOR。

## 乘法的精度

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

<!-- source: p1245 -->

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