J.5 浮点加法
浮点运算通常接收两个 位精度的输入,返回 位结果。理想算法先精确执行运算,再按当前舍入模式舍入到 位。上一节的乘法正是如此。实现 IEEE 算术的硬件必须给出与理想算法相同的结果,却不一定真的执行理想算法;加法就有更好的方法。
先看 6 位二进制数 与 相加。对阶后为
用 6 位加法器、暂时丢弃第二加数的低位,得到
第一个被丢弃的位为 1,但仅凭它无法判断是否进一,还需检查余下的 0001。其实只要像乘法那样用 sticky 位记录其中是否有非零位即可。因此,两个 位数相加,用 位加法器已足够,只需另存第一个丢弃位(舍入位),以及其余丢弃位的 OR(sticky 位),再按图 J.11 判断。本例 sticky 为 1,需要进一,最终为 。
再看:
6 位加法器得到:
由于左侧产生进位,舍入位不再是第一个被丢弃的位,而变成和的最低位 1。丢弃的 01 作 OR 得 sticky=1。两者均为 1,所以和的高 6 位 应进一,结果为 。
再看减法:
最简单的计算方式是把 转成补码,使差变成和:
6 位加法器得到:
最高位抵消后,必须把第一个丢弃位,即保护位,补到结果的最低端,得到 ;第二个丢弃位则成为舍入位。这与乘法算法的情况 (1) 相似。舍入位为 1 还不足以作决定,剩余 0001 的 OR 给出 sticky=1,所以最终进一为 。可见,减法抵消最高位时,需要一个保护位。若最高两位都抵消,是否就需要两个保护位?不需要。如果 足够接近,以至于差的最高两位抵消,那么 会是精确的,根本不需要保护位。
加法比乘法复杂,是因为它可能因操作数符号而实际变成减法。加法可能在左侧产生进位;减法可能发生抵消;两者都会改变舍入位的位置。不过,无需先求精确和再舍入,只要用最高 位的和,以及舍入位、sticky 位,就能确定结果。
下面详细给出浮点加法算法。令两个加数为 ,指数和有效数分别为 。输入已经解包,因此 含显式首位。算法分八步:
- 若 ,交换操作数,使 。暂定结果指数为 。
- 若 异号,把 替换成其补码。
- 把 放入 位寄存器,右移 位;若上一步取过补码,高位移入 1。移出的位中,最高一位置入 ,次高位置入 ,其余位的 OR 置入 sticky。
- 把 与保存 的 位寄存器相加,得到初步有效数 。若两加数异号、S 最高位为 1、且没有进位输出,则 S 为负,需用其补码替换。这只可能发生在 时。
- 若两加数同号,且第 4 步有进位输出,则 S 右移一位,高位移入进位 1。否则,左移直到规格化;第一次左移时把 移入低位,之后移入 0。相应调整结果指数。
- 调整 。若第 5 步右移,令 为移位前 S 的最低位,;若不移位,令 、;若左移一次, 不变;若左移至少两次,令 。原书解释,最后一种情况仅发生于两加数异号且指数相同,此时第 4 步计算精确。
- 按图 J.11 舍入 S:对应条件成立时,在最低位加 1。若产生进位,则右移 S 并调整指数。这样得到最终有效数。
- 确定符号。若两加数同号,结果同号;若异号,结果取决于原来哪个为负、第 1 步是否交换,以及第 4 步是否对 S 取补码,见图 J.12。
译注:第 6 步的赋值应按调整前的 同时计算,不能先覆盖 再计算 。原书“左移至少两次只会发生于指数相同”过于严格:指数相差 1 也可能严重抵消,例如 ;该差同样精确,故置 的处理仍适用。
| 第 1 步交换? | 第 4 步取补码? | 原 符号 | 原 符号 | 结果符号 |
|---|---|---|---|---|
| 是 | 任意 | + | − | − |
| 是 | 任意 | − | + | + |
| 否 | 否 | + | − | + |
| 否 | 否 | − | + | − |
| 否 | 是 | + | − | − |
| 否 | 是 | − | + | + |
例题 用上述算法计算 。
解答 初始 。
- ,交换;,暂定指数为 0。
- 两操作数都为负,不对 取负。
- 交换后的 右移两位,得 。
- ,因此 S=.001,并有进位。
- 右移并补入进位,S=1.000,指数增为 1。
- 舍入位是原和的最低位,即 1;sticky 为 。
- 为真,按图 J.11 进一,得 S=1.001。
- 两加数均为负,结果为负。原页末式写作 。
译注:原页末式右侧漏了负号;与题目及本步文字一致的结果应为 。
例题 计算 。
解答 。
- 不交换,,暂定指数为 0。
- 异号,把 替换成补码 0.100。
- ,不移位,。
- 。异号、最高位为 1、无进位,因此对和取补码,得 S=0.010。
- 左移两位,S=1.000,指数减为 −2。
- 左移两次,。
- 舍入不需要加 1。
- 结果为 。查图 J.12:取过补码、未交换、原 为负,所以结果为正,即 。
加速加法
估算上述算法的时间:第 2 步可能需要一次加法,第 4 步需要一到两次,第 7 步可能再需一次。若 位加法耗时 T,单精度 、双精度 ,看起来至少需要 4T。但这过于悲观:第 4 步若需两次加法,两输入必然同指数、异号,差是精确的,第 7 步无需进一,所以最多三次加法。
同样,第 3、5 步似乎各需一次可变移位,但若 ,第 3 步最多右移一位,只有第 5 步需要可变移位;若 ,第 3 步需要可变移位,第 5 步却最多左移一位。因此最多只执行一次可变移位。即便如此,三次串行加法对 53 位双精度有效数来说仍很耗时。
加速方法之一是流水线,J.10 节会介绍商业芯片实例。另一种是并行进行两次加法,Intel 860 就采用了它 [Kohn and Fu 1989];下面说明它如何把延迟从 3T 降为 T。
分三种情况。首先,两操作数同号,需要合并第 4、7 步。第 4 步是否产生进位尚不确定,因此和的最高位位置未知;用两个加法器分别覆盖两种可能。第一个假定无进位,因而可事先算出 。若应进一,计算 ,其中加 1 的位置是 的最低位,可通过把普通加法器的最低进位输入置 1 实现;否则照常计算 。若 ,还需知道和的最低位,它也能很快预先算出。第二个加法器假定会进位,此时 和加 1 的位置不同,但也能事先算出。究竟是否有进位,要到加法完成后才知道;两个加法器并行运行,必有一个产生正确答案。
其次,两输入异号、同指数。此时和精确,无需舍入,但结果符号要等加法结束才知道。不要在第 2 步先取补码,因为那要一次加法;改为并行计算 与 。前者把对 取补码与求和合并,得到 ;后者得到 。
其中一个非负,就是最终结果的绝对值。这样,所有加法仍由两个并行加法器在一步完成。
最后,两输入异号、指数不同,情况更复杂。若 ,差的首位仅可能在两个位置之一,类似普通加法。若 ,可能发生抵消,首位几乎可以出现在任何位置;不过,只有差的首位与 首位同位置时才可能需要进一。因此,一个加法器假定需要进一,另一个假定不需要,即可合并第 4、7 步。但第 2 步的加法怎么办?
考虑第 4 步中两个操作数的对齐关系:
若标为 z 的位全为 0, 的高 位可用 算出;若至少一个 z 为 1,则用 。因此一次加法就能得到差,但仍需取得 补码对应的 ,供第 7 步舍入。
为此,将第 2、4 步合并:第 2 步不对 取补码;把加法器向右扩展两位,扩展的和记为 。在 右侧补两个 0 得 ,在 后附上初步的 得 。若第 3 步得到的初步 sticky 为 1,计算 ;若 sticky 为 0,则计算 。现在, 的最低两位就是正确的 ,sticky 已在第 3 步正确算出。再与第 4、7 步的合并方法结合,就能在一次加法时间 T 内得到结果。
还有一些细节,见 Santoro、Bewick 与 Horowitz [1989] 及习题 J.17。该论文虽针对乘法,但很多讨论同样适用于加法。习题 J.19 还给出了另一种符号—数值形式的加法方法。
非规格化数
与乘法不同,即使一个输入为非规格化数,以上加法算法也几乎不变。只需检测指数域是否为 0;若为 0,解包有效数时不补首位 1,并把内部偏置指数置为 1,算法其余部分照常运行。
处理非规格化输出时,第 5 步略作修改:左移 S,直到规格化,或指数达到 ,即偏置指数为 1。若指数为 ,且舍入后 S 的最高位为 1,结果仍是规格化数,按通常方式打包,省去首位 1。
若最高位为 0,则结果是非规格化数,存回格式时指数域应置 0。J.7 节将讨论检测下溢的精确规则。
译注:原文此处写“结果解包时”(unpacked),但语境是把内部结果编码回浮点格式,即打包;译文据此表述,并保留此说明。
上溢很容易检测:只有第 5 步右移时,偏置指数被增至单精度的 255、双精度的 2047,或者舍入后发生同样情况,才会出现上溢。