Negative Number Representation
表示负数的习惯方法是在前面加一个负号。 将 - 放在 3 前面会生成 -3。这种表示负数的方法称为符号幅度【signed magnitude】表示法。它可以在计算机上通过为符号位分配一个存储位来实现。要更改使用符号幅度【signed magnitude】表示法存储的数字的符号,只需翻转符号位即可。计算机上的整数通常不使用符号幅度【signed magnitude】表示法。原因是,为了用符号幅度【signed magnitude】表示法做加法,你需要按照教给孩子的方式来做(就是算是方程式)。
Signed Magnitude Addition
-
x 和 y 相加:
-
情况一:如果 x 和 y 均为正数,只需相加即可得出正数。
-
情况二. 如果 x 和 y 均为负数,则删除负号,将数字相加,然后在结果上添加一个负号。
-
情况三. 如果 x 和 y 具有不同的符号,则从较大的幅度中减去较小的幅度,如果较大的幅度有负号,则将其附加到结果上。
用芯片上的逻辑电路实现符号幅度加法将是对晶体管的严重浪费。
Easy Addition
实际使用逻辑电路来实现 x 和 y 的加法的方法如下:
- 不关心 x 和 y 是正数还是负数; 只需对它们相加即可!
这样的方法看起来根本就不是方法。但它是有效的。 它之所以有效,是因为使用了一种表示负值的特殊系统,并且寄存器的大小是有限的,就像里程表一样。
Odometer Arithmetic
里程表是一个加法器【adding machine】。 它会累加您驾驶的里程。 里程总是被认为是一个正值。(负值是否意味着向后行驶,或者什么?)但是,请考虑以下等式,我们通常认为该等式仅对负数有意义。
Table 3.8. Odometer Negatives
−1 | 99,999 |
−2 | 99,998 |
−3 | 99,997 |
−4 | 99,996 |
−5 | 99,995 |
−6 | 99,994 |
−7 | 99,993 |
−8 | 99,992 |
−9 | 99,991 |
−10 | 99,990 |
−11 | 99,989 |
−12 | 99,988 |
. | . |
etc. | . |
显而易见的解是 x = −5。 然而,这个方程也有里程表解释。 假设您驾驶汽车行驶了 10 英里,完成时里程表读数为 5,那么你开始时里程表上显示的是多少? 它一定是 99,995。 同样,如果您的汽车里程表读数为 10 英里,而您驾驶了 99,995 英里,那么当您完成行驶时,里程表读数应为 5 英里。 这里的要点是,当在里程表上做加法时,99,995 的作用就像 -5 一样。 表 3.8 显示了里程表负数的样子。 请注意,要获得作为数字 x 的负数的数字,您需要从 100,000 中减去 x。
里程表上的算术是这样工作的,因为里程表在 100,000 英里时翻转。 计算机寄存器也会周转,但不是 100,000。
Register Arithmetic
四位寄存器在二进制 1 0000 = = 16 时翻转。一个四位寄存器的负数表可以很容易地完整列出来,参见表 3.9。 这个表和里程表唯一的区别就是 16 比 100,000 小很多。 与里程表一样,您可以使用减法而不是表格来查找作为某个数字 x 的负数的数字。 对于四位寄存器,您可以从 16 而不是从 100,000 减去 x,其他都是一样的。
Decimal | Binary | |
---|---|---|
−1 | 15 | 1111 |
−2 | 14 | 1110 |
−3 | 13 | 1101 |
−4 | 12 | 1100 |
−5 | 11 | 1011 |
−6 | 10 | 1010 |
−7 | 9 | 1001 |
−8 | 8 | 1000 |
−9 | 7 | 0111 |
−10 | 6 | 0110 |
−11 | 5 | 0101 |
−12 | 4 | 0100 |
−13 | 3 | 0011 |
−14 | 2 | 0010 |
−15 | 1 | 0001 |
Table 3.9. Four-Bit Register Negatives (Ambiguous!)
但从某一方面来看,表 3.9 有点令人震惊。 例如,它告诉我们 11 与 −5 的作用相同。 这意味着我们现在有一个严重的一语多意/歧义【ambiguity】。 如果一个四位寄存器包含 1011,我们无法判断它实际上意味着 11 还是 -5,这需要一些约定。 惯例是把第一个位作为符号位,当第一位为 0 时,该数字为正数(或零)。 当第一位为1时,该数为负数。 由此产生的系统称为补码系统。 当使用补码系统时,一语多意就变成了:
Decimal | Binary | |
---|---|---|
−1 | 1111 | |
−2 | 1110 | |
−3 | 1101 | |
−4 | 1100 | |
−5 | 1011 | |
−6 | 1010 | |
−7 | 1001 | |
−8 | 1000 | |
7 | 0111 | |
6 | 0110 | |
5 | 0101 | |
4 | 0100 | |
3 | 0011 | |
2 | 0010 | |
1 | 0001 |
Table 3.10. Four-Bit Two's Complement Representation
请注意,虽然补码系统中的第一位是符号位,但它不仅仅是符号位。 例如,如果我们更改 1011 上的符号位,我们会得到 0011。这会将 -5 更改为 3,而不是 5。
Signed vs. Unsigned Numbers
如果计算机上完成的所有算术都是使用补码表示形式完成的,那么解释数字寄存器的内容永远不会有任何问题。 但不幸的是事实并非如此。 有时在进行算术运算时,假设数字是使用普通二进制系统表示的。 那么,当一个四位寄存器中有 1011 时,这意味着 -5 还是 11? 如果我们使用补码,那么我们知道它是-5。 但我们可以只使用普通的二进制! 然后我们就有了11。计算实践在这方面并不一致,两种系统均被使用,但可以是任意一个,由软件来区分。 经常使用术语“有符号”与“无符号”。 有符号整数是补码整数。 无符号整数是普通的二进制整数。
尽管当我们对数字进行相加时,数字是有符号还是无符号并不重要——加法硬件可以以任何方式工作——但许多其他操作需要不同的实现,这具体取决于操作数是有符号数字还是无符号数字。 考虑不等式测试【inequality testing】, 假设要求四位硬件确定 1011 < 0010 是否为真。 0010 在有符号和无符号系统中均表示 2,而 1011 作为有符号数表示为 -5,作为无符号数表示为 11。 有符号解释 −5 < 2 产生 true,而无符号解释 11 < 2 产生 false。 在这两种情况下硬件必须做出不同的响应。 这意味着一个单独的“小于”命令无法工作。 需要两个,一个用于有符号数,一个用于无符号数。 这个问题在汇编语言中反复出现。
在C语言中,整数可以声明为 unsigned int
或 signed int
。 未指定的 int 声明与 signed int
含义相同。
Subtraction Using Negation
本节解释如何使用迄今为止描述的电路进行减法。
Two's Complementation
由于我们有一个可以处理负数的加法器,因此我们应该能够使用它来进行减法,例如 5 − 3,因为:
但有一个问题。 将 3 转换为 −3 并不像上面加上一个负号或翻转一个位那么容易。 如果是这样,则不需要表 3.8 和表 3.9 这样的表。 尽管我们确实有一个公式可以解释这些表格,但不幸的是它是基于减法的。 例如,根据四位表的公式,要得到 3 的负数,您需用 16 减去 3。但是由于我们的目标正是找到一种做减法的方法,我们似乎陷入了一个恶性循环。 然而,情况并不像看起来那么糟糕。 即使我们不知道如何减法,即使 16 的二进制表示比我们的四位寄存器还要多一位,也可以实现二进制的 16 减 3。 有两个事实拯救了我们:
- 16−x = (15−x) + l.
- 只需翻转位即可完成 15 的减法。 注意当我们用二进制的 15 减去 3 时会发生什么。
0011 中的位被翻转,我们得到 1100。我们有可以翻转位的硬件。 我们可以将每一位输入非门。 加 1 即可得到结果。
加法电路中未使用的输入在这里发挥作用(参见第 3.4 节),这正是总数加 1 所需要的。 翻转位并加一的过程称为补码【two's complementation】。 它从翻转额【即16】中减去一个数字。 n 位补码数的翻转值为 。 如果 n = 4,则该值为 16。
所以我们可以用加法的形式来做 5 − 3:
在了解了计算机的底层后,他的实现其实是:
最后的进位位被忽略。 这就是计算机如何从 5 中减去 3。
这里使用补【complement】这个词是合理,究其原因是:
请注意,这忠实地反映了这样一个事实(译者注:因为 16 - x 求的是 x 的负数,我们就可以替换为 -x ,然后外层又是一个 16 - x 的模式,我们继续替换,就可以得到下面的方程):
Two's Complementation in Hex
补码过程是通过翻转位并加一来完成的。 如果我们考虑翻转十六进制数字的位,我们会发现这可以通过从 15 中减去它来完成。为了计算十六进制数字的补码,我们可以从 15 中减去它的每一位,然后加 1。 例如,给定一个八位数字 23H,
因此 23H 的补码是 DDH。 另一方面,如果我们利用找到补码实际上是从翻转值中减去它这一事实,我们就可以直接进行减法:
Conversions
当调试器为我们显示寄存器内容时,负数将以十六进制显示给我们。 因此,熟悉十六进制补码值是值得的。 假设我们看到 16 位值 FF42H,我们想知道它代表什么值。 首先我们观察到,如果我们将其视为一个有符号数,那么它是负数,因为它的第一位是 1 - 如果第一个十六进制数字是任何十六进制数字 8、9、A、B、C、D、E、 或F,则第一个二进制数为1。要要找到对应的正数,我们可以用十六进制来计算这两个数的补码【two's complement】。
BEH 为 11 × 16 + 14 = 190。因此,表示值为 -190。
您还可以将所有内容转换为二进制,然后取补码,然后转换为十进制。 当然你也可以将 FF42H 转换为十进制,然后用 65,536 减去它求得结果! 尽可能多地使用十六进制可能是最简单的。
* Placeholding Two's Complement
补码可以被认为是一个占位系统【placeholding system】。 如果最高有效位的位值被认为代表其普通二进制值的负数,那么这将产生补码编号的位值解释。 表 3.11 显示了四位二进制补码系统中的位值。 通过该表,我们看到 −5 可以表示为(−8) + 2 + 1。
Table 3.11. Four-Bit Two's Complement Place Values
−8s | 4s | 2s | 1s | Binary | Decimal |
---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1111 | −1 |
1 | 1 | 1 | 0 | 1110 | −2 |
1 | 1 | 0 | 1 | 1101 | −3 |
1 | 1 | 0 | 0 | 1100 | −4 |
1 | 0 | 1 | 1 | 1011 | −5 |
1 | 0 | 1 | 0 | 1010 | −6 |
1 | 0 | 0 | 1 | 1001 | −7 |
1 | 0 | 0 | 0 | 1000 | −8 |
0 | 1 | 1 | 1 | 0111 | 7 |
0 | 1 | 1 | 0 | 0110 | 6 |
0 | 1 | 0 | 1 | 0101 | 5 |
0 | 1 | 0 | 0 | 0100 | 4 |
0 | 0 | 1 | 1 | 0011 | 3 |
0 | 0 | 1 | 0 | 0010 | 2 |
0 | 0 | 0 | 1 | 0001 | 1 |