2.3.2 补码加法
2.3.2 补码加法
对于补码加法,我们必须确定当结果太大(为正)或者太小(为负)时,应该做些什么。给定在范围 -2w-1 ≤ x, y ≤ 2w-1 - 1 之内的整数值 x 和 y,它们的和就在范围 -2w ≤ x + y ≤ 2w - 2 之内,要想准确表示,可能需要 w + 1 位。就像以前一样,我们通过将表示截断到 w 位,来避免数据大小的不断扩张。然而,结果却不像模数加法那样在数学上感觉很熟悉。定义 x +tw y 为整数和 x + y 被截断为 w 位的结果,并将这个结果看做是补码数。
原理:补码加法
对满足 -2w-1 ≤ x, y ≤ 2w-1 - 1 的整数 x 和 y,有:
| x +tw y | 条件 | 情况 |
|---|---|---|
| x + y - 2w | 2w-1 ≤ x + y | 正溢出 |
| x + y | -2w-1 ≤ x + y < 2w-1 | 正常 |
| x + y + 2w | x + y < -2w-1 | 负溢出 |
(2.13)
图 2-24 说明了这个原理,其中,左边的和 x + y 的取值范围为 -2w ≤ x + y ≤ 2w - 2,右边显示的是该和数截断为 w 位补码的结果。(图中的标号“情况 1”到“情况 4”用于该原理形式化推导的案例分析中。)当和 x + y 超过 TMaxw 时(情况 4),我们说发生了正溢出。在这种情况下,截断的结果是从和数中减去 2w。当和 x + y 小于 TMinw 时(情况 1),我们说发生了负溢出。在这种情况下,截断的结果是把和数加上 2w。
两个数的 w 位补码之和与无符号之和有完全相同的位级表示。实际上,大多数计算机使用同样的机器指令来执行无符号或者有符号加法。

推导:补码加法
既然补码加法与无符号数加法有相同的位级表示,我们就可以按如下步骤表示运算 +tw:将其参数转换为无符号数,执行无符号数加法,再将结果转换为补码:
x +tw y ≐ U2Tw(T2Uw(x) +uw T2Uw(y)) (2.14)
根据等式 (2.6),我们可以把 T2Uw(x) 写成 xw-12w + x,把 T2Uw(y) 写成 yw-12w + y。使用属性,即 +uw 是模 2w 的加法,以及模数加法的属性,我们就能得到:
x +tw y = U2Tw(T2Uw(x) +uw T2Uw(y))
= U2Tw[(xw-12w + x + yw-12w + y) mod 2w]
= U2Tw[(x + y) mod 2w]
消除了 xw-12w 和 yw-12w 这两项,因为它们模 2w 等于 0。
为了更好地理解这个数量,定义 z 为整数和 z = x + y,z' 为 z' = z mod 2w,而 z'' 为 z'' = U2Tw(z')。数值 z'' 等于 x +tw y。我们分成 4 种情况分析,如图 2-24 所示。
-2w ≤ z < -2w-1。然后,我们会有 z' = z + 2w。这就得出 0 ≤ z' < -2w-1 + 2w = 2w-1。检查等式 (2.7),我们看到 z' 在满足 z'' = z' 的范围之内。这种情况称为负溢出(negative overflow)。我们将两个负数 x 和 y 相加(这是我们能得到 z < -2w-1 的唯一方式),得到一个非负的结果 z'' = x + y + 2w。
-2w-1 ≤ z < 0。那么,我们又将有 z' = z + 2w,得到 -2w-1 + 2w = 2w-1 ≤ z' < 2w。检查等式 (2.7),我们看到 z' 在满足 z'' = z' - 2w 的范围之内,因此 z'' = z' - 2w = z + 2w - 2w = z。也就是说,我们的补码和 z'' 等于整数和 x + y。
0 ≤ z < 2w-1。那么,我们将有 z' = z,得到 0 ≤ z' < 2w-1,因此 z'' = z' = z。补码和 z'' 又等于整数和 x + y。
2w-1 ≤ z < 2w。我们又将有 z' = z,得到 2w-1 ≤ z' < 2w。但是在这个范围内,我们有 z'' = z' - 2w,得到 z'' = x + y - 2w。这种情况称为正溢出(positive overflow)。我们将正数 x 和 y 相加(这是我们能得到 z ≥ 2w-1 的唯一方式),得到一个负数结果 z'' = x + y - 2w。■
图 2-25 展示了一些 4 位补码加法的示例作为说明。每个示例的情况都被标号为对应于等式 (2.13) 的推导过程中的情况。注意 24 = 16,因此负溢出得到的结果比整数和大 16,而正溢出得到的结果比之小 16。我们包括了运算数和结果的位级表示。可以观察到,能够通过对运算数执行二进制加法并将结果截断到 4 位,从而得到结果。
| x | y | x + y | x +t4 y | 情况 |
|---|---|---|---|---|
| -8 [1000] | -5 [1011] | -13 [10011] | 3 [0011] | 1 |
| -8 [1000] | -8 [1000] | -16 [10000] | 0 [0000] | 1 |
| -8 [1000] | 5 [0101] | -3 [11101] | -3 [1101] | 2 |
| 2 [0010] | 5 [0101] | 7 [00111] | 7 [0111] | 3 |
| 5 [0101] | 5 [0101] | 10 [01010] | -6 [1010] | 4 |
图 2-25 补码加法示例。通过执行运算数的二进制加法并将结果截断到 4 位,可以获得 4 位补码和的位级表示
图 2-26 阐述了字长 w = 4 的补码加法。运算数的范围为 -8~7 之间。当 x + y < -8 时,补码加法就会负溢出,导致和增加了 16。当 -8 ≤ x + y < 8 时,加法就产生 x + y。当 x + y ≥ 8 时,加法就会正溢出,使得和减少了 16。这三种情况中的每一种都形成了图中的一个斜面。

等式 (2.13) 也让我们认出了哪些情况下会发生溢出:
原理:检测补码加法中的溢出
对满足 TMinw ≤ x, y ≤ TMaxw 的 x 和 y,令 s = x +tw y。当且仅当 x > 0,y > 0,但 s ≤ 0 时,计算 s 发生了正溢出。当且仅当 x < 0,y < 0,但 s ≥ 0 时,计算 s 发生了负溢出。
图 2-25 显示了当 w = 4 时,这个原理的例子。第一个条目是负溢出的情况,两个负数相加得到一个正数。最后一个条目是正溢出的情况,两个正数相加得到一个负数。
推导:检测补码加法中的溢出
让我们先来分析正溢出。如果 x > 0,y > 0,而 s ≤ 0,那么显然发生了正溢出。反过来,正溢出的条件为:1)x > 0,y > 0(或者 x + y > TMaxw),2)s ≤ 0(见公式 (2.13))。同样的讨论也适用于负溢出情况。■
练习题 2.29
按照图 2-25 的形式填写下表。分别列出 5 位参数的整数值、整数和与补码和的数值、补码和的位级表示,以及属于等式 (2.13) 推导中的哪种情况。
| x | y | x + y | x +t5 y | 情况 |
|---|---|---|---|---|
| [10100] | [10001] | |||
| [11000] | [11000] | |||
| [10111] | [01000] | |||
| [00010] | [00101] | |||
| [01100] | [00100] |
练习题 2.30
写出一个具有如下原型的函数:
/* Determine whether arguments can be added without overflow */
int tadd_ok(int x, int y);如果参数 x 和 y 相加不会产生溢出,这个函数就返回 1。
练习题 2.31
你的同事对你补码加法溢出条件的分析有些不耐烦了,他给出了一个函数 tadd_ok 的实现,如下所示:
/* Determine whether arguments can be added without overflow */
/* WARNING: This code is buggy. */
int tadd_ok(int x, int y) {
int sum = x+y;
return (sum-x == y) && (sum-y == x);
}你看了代码以后笑了。解释一下为什么。
练习题 2.32
你现在有个任务,编写函数 tsub_ok 的代码,函数的参数是 x 和 y,如果计算 x - y 不产生溢出,函数就返回 1。假设你写的练习题 2.30 的代码如下所示:
/* Determine whether arguments can be subtracted without overflow */
/* WARNING: This code is buggy. */
int tsub_ok(int x, int y) {
return tadd_ok(x, -y);
}x 和 y 取什么值时,这个函数会产生错误的结果?写一个该函数的正确版本(家庭作业 2.74)。