原码、反码和补码

试图用一篇文章解决你的所有困惑

Posted by Mike Lyou on September 5, 2021

有符号数和无符号数

当我们声明一个变量时,自己应该清楚这是有符号数,还是无符号数。

无符号数,就是没有符号位,所有位都用来表示数值。

有符号数,其最高位是符号位(通常正数为0,负数为1),剩下位用来表示数值。

例如,8 位的无符号数和有符号数的对比:

1
2
3
			最小值					最大值
无符号数	0000 0000 (0)		1111 1111 (255)
有符号数	1111 1111 (-127)	0111 1111 (127)

有的读者可能注意到,无符号数的范围是 0~255 ,宽度为 256,而有符号数的范围是 -127~127, 宽度似乎是 255,二者宽度竟然不一致。这是因为有符号数中存在 -0 的缘故。如果不理解也没关系,这一点我会在讲反码时详细解释。

机器数和真值

机器数,是指一个数在计算机中的二进制表示。机器数的最高位是符号位,正数符号位为0,负数符号位为1。

真值,是带符号位的机器数对应的实际数值。将机器数的符号位替换为正负号,就等同与真值。

例如:

1
2
3
十进制		 	机器数				真值
+6			0000 0110		+000 0010		
-3			1000 0011		-000 0011

原码、反码和补码

机器数包含原码、反码和补码三种表示形式。

原码

原码是一种计算机中对数字的二进制表示法,其最高位为符号位,正数为0,负数为1. 上文中的机器数使用的就是原码。

原码对于人类来说容易理解,但是符号位的存在,使得计算机无法使用简单的方法对其进行运算。因此,需要引入反码。

反码

一个数字用原码表示是容易理解的,但是需要单独一个位来表示符号位,并且在进行加法时,计算机需要先识别某个二进制原码是正数还是负数,识别出来之后再进行相应的运算。这样效率不高,能不能让计算机在进行运算时不用去管符号位,也就是让符号位参与运算。要实现这个功能,我们就要用到反码。

反码是一种在计算机中数的机器码表示。正数的反码和原码一样,负数的反码就是在原码的基础上符号位保持不变,其他位取反。(取反操作:就是将 0 变为 1,1 变为 0)。例如:

1
2
3
4
十进制		 	原码				反码
+6			0000 0110		0000 0010
+3			0000 0011		0000 0011
-3			1000 0011		1111 1100

下面我们来看一下用反码直接运算会是什么情况,我们以 3 + (-3) 为例。

1
2
3
4
5
6
7
3 - 3 ==> 3 + (-3)

0000 0011 // 6(反码)
+ 1111 1100 // -3(反码)
----------------------
1111 1111 // (反码)
1000 0000 // -0(原码)   

我们发现,3 + (-3) 的结果是 -0,结果是正确的,似乎没有什么问题。但注意到 0-0 是一样的数制,却有两种不同的机器数,这不是我们所期望的,从中我们意识到反码存在一定的问题。我们再看一个例子,这个例子能明确的显示出反码的问题所在。

让我们计算一下 6 + (-3)

1
2
3
4
5
6
6 - 3 ==> 6 + (-3)
0000 0110 // 6(反码)
+ 1111 1100 // -3(反码)
----------------------
0000 0010 // (反码)
0000 0010 // 2(原码)

震惊,6 + (-3) 竟然等于 2

通过以上两个示例,我们发现使用反码进行加法运算并不能保证得出正确的结果。原因是这些数字中多了一个 -0。为了解决反码出现的问题,我们需要引入补码。

补码

补码是一种用二进制表示有符号数的方法。正数和 0 的补码就是该数字本身。负数的补码则是将其对应正数按位取反再加 1。

补码系统的最大优点是可以在加法或减法处理中,不需因为数字的正负而使用不同的计算方式。只要一种加法电路就可以处理各种有符号数加法,而且减法可以用一个数加上另一个数的补码来表示,因此只要有加法电路和补码电路即可以完成各种有符号数加法和减法,在电路设计上相当方便。因此计算机中的数值都采用补码的形式。

通过 +1 的操作,我们发现 -0 (反码为1000 0000)的补码为 0000 0000,也就是 0。补码的系统只有一个 0,消除了导致结果不正确的 -0

1
2
3
十进制		原码				反码			补码
-0		1000 0000		1111 1111		0000 0000
0		0000 0000		0000 0000		0000 0000

有了反码之后,我们再来重新计算一下 6 + (-3)

1
2
3
4
5
6
7
8
十进制		原码				反码			补码
+6		0000 0011		0000 0011		0000 0011
-3		1000 0011		1111 1100		1111 1101

0000 0110 //6(补码)
+ 1111 1101 //-3(补码)
----------------------
0000 0011 //3(补码)

结果正确。


思考:从反码到补码,-0 不复存在,但是一个字节(1 Byte = 8 bits)的数字能表示的整数是固定的(128个),既然消失了一个数,就应该多出来一个新的数,是哪个数呢?

答案:是 -128。因为最高位是符号位,所以剩下七位能表示的最大绝对值为 127,故 -128 是没有原码的,当然也没有反码,只有在 -0 消失掉的补码世界中才存在。

1
2
3
4
5
6
7
十进制			原码				反码			补码
127			0111 1111		0111 1111		0111 1111
1			0000 0001		0000 0001		0000 0001
0			0000 0000		0000 0000		0000 0000
-1			1000 0001		1000 0001		1000 0001
-127		1111 1111		0000 0000		0000 0001
-128		---- ----		---- ----		1000 0000

参考资料


Author: Mike Lyou
Link: https://blog.mikelyou.com/2021/09/05/number-encoding/
Liscense: This work is licensed under a CC BY-NC-SA International License.