0%

搞懂原码、反码、补码

在本文开始前,先抛出一个问题:在计算机系统中,数字一律用补码的形式来表示和存储。为什么?

希望通过本文能够解释这个问题。

重要:为了简洁说明,约定本文以下内容中都使用一个字节,也就是 8 个 bit 来表示二进制数。

原码

原码(True form)是指“未经更改”的码,是指一个二进制数左边加上符号位后所得到的码。

计算机中所有的数字均用 0 和 1 编码表示,数字的正负号也不例外,如果一个机器数字长是 n 位的话,约定最左边 1 位用作符号位,其余 n - 1 位用于表示数值,这部分也称为数值域。

因此,当二进制数大于 0 时,符号位为 0;当二进制数小于 0 时,符号位为 1;当二进制数等于 0 时,符号位可以为 0 或 1 (+0 / -0)。

举个例子:

十进制 原码
2 0000 0010
-2 1000 0010

可以看到,一个数字用二进制原码表示的话,其取值范围是 -111 1111 ~ +111 1111,换算成十进制就是 -127 ~ +127。

为了便于算术逻辑单元(Arithmetic Logic Unit, ALU)的设计,后面又发展出了反码、补码等经过转换过的码。

反码

对于二进制数而言,对其进行取反操作就是将 0 变为 1,1 变为 0。

正数的反码(Ones’ complement)等于其原码,负数的反码则通过保留其符号位,然后将原码的数值位取反得到。

举个例子:

十进制 原码 反码
2 0000 0010 0000 0010
-2 1000 0010 1111 1101

为什么会出现反码?

在数学中有加减乘除四则运算,但这对于计算机而言却太麻烦了。我们希望最好能够只有一种情况,比如只有加法,这样才能够让计算机变得简单高效。

而这完全可以做到。举个例子,在数学中 5 - 3 = 2,它完全等价于 5 + (-3) = 2,这样就做到了用加法来表示减法,而乘法是加法的累积(例如 3 * 5 = 3 + 3 + 3 + 3 + 3 = 15),除法是减法的累积(例如 10 / 3 = 10 - 3 - 3 - 3 = 1 mod 3)。所以在计算机中只有加法就够了。

但是,如果直接用原码来进行加法,计算机需要先识别该二进制原码的符号位,来确定该数字是正数还是负数,然后再进行加法运算,这样显然效率不高。

为了提高效率,让计算机能够在运算时统一处理符号位和数值域,或者说让符号位也能参与运算,所以就产生了反码。

至此,问题好像得到了解决。接下来我们验证一下。

仍然以上面的 5 - 3 为例:5 的原码为 0000 0101,反码为 0000 0101;-3 的原码为 1000 0011,反码为 1111 1100。

1
2
3
4
5
6
5 - 3
= 5 + (-3)
= 0000 0101(反码) + 1111 1100(反码)
= 0000 0001(反码)
= 0000 0001(原码)
= 1

结果竟然差了 1 ?!

不急,我们再来看两个特殊的运算:

1
2
3
4
5
6
1 - 1
= 1 + (-1)
= 0000 0001(反码) + 1111 1110(反码)
= 1111 1111(反码)
= 1000 0000(原码)
= -0
1
2
3
4
5
0 + 0
= 0000 0000(反码) + 0000 0000(反码)
= 0000 0000(反码)
= 0000 0000(原码)
= 0

可以看到,虽然 -0 和 0 是一样的,但是在反码中却有两种表示方式。也可以这么理解:当用一个字节表示数字的取值范围时,这些数字中多了个 -0。

因此,虽然用反码进行运算时符号位可以直接参加运算,但是结果却是不对的。

补码

补码(Two’s complement)的出现就是为了解决上面反码的问题。

正数的补码和其原码、反码一样,负数的补码是其反码 + 1。

举个例子:

十进制 原码 反码 补码
2 0000 0010 0000 0010 0000 0010
-2 1000 0010 1111 1101 1111 1110

同样,接下来我们用补码验证一下。

仍然以 5 - 3 为例:5 的原码为 0000 0101,反码为 0000 0101,补码为 0000 0101;-3 的原码为 1000 0011,反码为 1111 1100,补码为 1111 1101。

1
2
3
4
5
6
5 - 3
= 5 + (-3)
= 0000 0101(补码) + 1111 1101(补码)
= 0000 0010(补码)
= 0000 0010(原码)
= 2

结果没问题。再来看那两个特殊的运算:

1
2
3
4
5
6
1 - 1
= 1 + (-1)
= 0000 0001(补码) + 1111 1111(补码)
= 0000 0000(补码)
= 0000 0000(原码)
= 0
1
2
3
4
5
0 + 0
= 0000 0000(补码) + 0000 0000(补码)
= 0000 0000(补码)
= 0000 0000(原码)
= 0

结果仍然没问题,至此,问题终于得到了解决。

同时这也解释了一开始的问题,在计算机系统中,数字一律用补码的形式来表示和存储。

原码、反码、补码,你搞懂了吗?


欢迎关注我的其它发布渠道