这篇文章上次修改于 398 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 背景

我们了解到计算机由控制器、运算器、存储器、输入和输出五个部分组成。其中,运算器中不包含减法器,倒不是说减法器实现不了,而是聪明的人发现了可以用加法器来实现减法操作,这样就不必再设计减法器了。比如,减法可以看成一个数加上另一个负数。这样的话,就需要引入符号位,即负号和正号。其实,原码、反码和补码的出现就是为了解决计算机中存储数字符号位的问题以及让计算机能够计算减法。

2 概念

2.1 符号位

n 位二进制系统可以产生 2^n 个不同的组合。如果必须用二进制表示有符号数,有个简单的方法,就是将这个空间分成两个相同的子集,一个子集用来表示正数,另一个表示负数。另外,编码规则应该按照如下原则进行:有符号数的引入应该使硬件实现的复杂度尽可能小。

可以将二进制位的最高位定义为符号位,符号位为 0 表示正数,符号位为 1 表示负数。

2.2 原码

从字面意义上,原码是“未经更改”的码。原码是最简单的机器数表示法,用最高位表示符号位,其它位为数值位,存放该数的二进制的绝对值。

例如,在 8 位二进制系统中,2 的原码是 00000010,-2 的原码 10000010。

8 位原码的范围:-127(11111111) —— +127(01111111)

n 位原码的范围:-[2^(n-1) - 1] —— +[2^(n-1) - 1]

2.3 反码

  • 对于正数来说,反码和原码保持一致。
  • 对于负数来说,符号位不变,剩余位(数值位)按位取反。

例如:

  • 1 的反码是 00000001,-2 的反码是 11111101,那么 1 + (-2) = (-1) 的计算过程为:00000001 + 11111101 = 11111110,而 11111110 是 -1 的反码。
  • 3 的反码是 00000011,那么 3 + (-2) = 1 的计算过程为:00000011 + 11111101 = 00000001(溢出时循环进位),而 0000001 是 1 的反码。

这样就成功用反码实现了计算减法的目标!

8 位反码的范围:-127(11111111) —— +127(01111111)

n 位反码的范围:-[2^(n-1) - 1] —— +[2^(n-1) - 1]

2.4 补码

我们看到,如果所有的数字都存储为反码,其实已经实现了计算减法的目标,那么为什么会有补码呢?都是因为有 0 这个特殊数字的存在。0 的反码会有两种形式,例如,在 8 位系统中,+0 的反码是 00000000,-0 的反码是 11111111,这样 0 这个数字在计算机中的编码就不是唯一的了。这对于计算机是不行的,任何数都只能有 1 个编码。

于是,聪明的人做了个一个决定:把 0 当成正数,即 +0,这样 0 的编码就只能是 00000000。那么 8 位二进制表示的正数范围仍然是: +0 —— +127。

但对于负数就必须要做调整,-0 必须让位,11111111 不能再表示 -0。可以把负数整体向后”挪动 1 位“,例如, 将 8 位二进制表示的负数范围从 -127 —— -0 变成 -128 —— -1。

那么怎么整体挪动 1 位呢?方法就是反码 + 1。11111111 编码不再表示 -0,而变成了 -1。顺着推,最小的编码 10000000 就是 -128。

我们给这个反码 + 1 又取了一个新的名字,叫补码。于是乎,补码的定义如下:

  • 对于正数来说,补码和原码保持一致。
  • 对于负数来说,补码是反码加 1。

8 位补码的范围:-128(10000000) —— +127(01111111)

n 位补码的范围:-2^(n-1) —— +[2^(n-1) - 1]

3 小结

符号位的引入是为了让计算机可以存储负数。

补码的引入是为了用加法器来计算减法,降低硬件实现复杂度。

正数的原码、反码、补码保持一致。

负数的反码是最高位(符号位)不变,其余位(数值位)按位取反。补码是反码加 1。