原码、反码、补码
这篇文章希望来讨论一下关于二进制的原码。反码、补码的一些理解,若有错误之处,还望多多指正。
计算机世界中数的表示
在计算机世界中整数的表示分为有符号的整数和无符号的整数,无符号数很简单,”所见即所得”直接使用该数的二进制表示整数值。但是使用无符号数有一个缺陷,那就是无法表示负数。所以引申出原码,反码,补码的概念来表示有符号的整数。
原码
原码分为两部分,符号位和数值位,字节的最大有效位(最左侧的一位)用于表示值的符号,剩下的位表示值。正数的最大有效位(符号位)为0,负数的最大有效位(符号位)为1。
正数的原码就是十进制转成二进制得到的二进制值,而负数是对应的正数转成二进制得到的二进制值,然后将最高位(符号位)置为1表示这是一个负数。
正数:原码=二进制值
负数:原码=最高位设置为1的二进制值
例:
10进制数:76 原码:01001100
10进制数:-10 原码:10001010
原码表示0有两种方式,00000000
(十进制的+0)和10000000
(十进制的-0)。这大大的增加数字电路的复杂性和设计难度。计算机需要进行两次比较来确定结果是否是零。
并且我们来看看一个正数的原码加上一个负数的原码会发生什么:
例:
计算 76-10=66
十进制 原码
76 01001100
+ -10 10001010
---------------------
66 11010110(-86)
可以看到原码以加法替代减法(1-1=1+(-1))时不能按照无符号数字的方式进行。这使得计算机必须为原码的减法提供不同的数学运算电路。
因为最高位表示为符号位,并且有两种零的表示方式,所以原码能表示的范围为[-2^(n-1)-1,2^(n-1)-1]
,无符号二进制值的表示范围为[0,2^n-1]
。
原码用来表示带符号数,虽然带来了些额外的问题(无法采用与无符号数相同的加法运算电路,两种零的表示方式),确实解决了无符号数无法表示负数这一问题。且通过最高位是0还是1,我们就可以判断出这个数是大于等于零还是小于等于零。
反码
反码方法采用与无符号数相反的二进制生成来表示负数(注:正数的反码表示与原码形式、无符号数形式一样),即负数用反码表示时,就是将该负数对应正数的二进制表示的所有位全部取反,或者是该负数的原码除符号位外全部取反(0->1,1->0
)。
从其正数的二进制表示全部取反得到其对应负数的反码和从其负数的原码除符号位外全部取反得到该负数的反码,两种方式其实是一样的。所有不同资料在叙述反码的时候描述上可能有区别。
正数:反码=原码
负数:反码=对应正数的二进制表示(原码,反码)按位取反(或,该负数对应的原码除符号位外按位取反)
同原码一样,反码的表示形式也有两种,00000000
(十进制的+0
)与11111111
(十进制的−0
)
例:
10进制数:-43 反码:11010100
表示范围:[-2^(n-1)-1,2^(n-1)-1]
对两个反码表示形式的数字做加法,首先需要进行常规的二进制加法,但还需要在和的基础上加上进位。
例:
二进制 十进制
00000010 +2
+ 11111110 -1
............ ...
1 00000000 0 <-- 错误答案
1 +1 <-- 加上进位
............ ...
00000001 1 <-- 正确答案
在上面的例子中,二进制加法仅仅得到了00000000
,这是一个错误的答案。只有当加上进位时才能得到正确答案(00000001
)。
反码这种数字表示系统通常出现在老式的计算机中;PDP-1
,CDC 160A
,UNIVAC 1100/2200
系列以及其它的一些计算机都使用反码算术。
Internet
协议IPv4
,ICMP
,UDP
以及TCP
都使用同样的16位反码检验和算法。虽然大多数计算机缺少“循环进位”硬件,但是这种额外的复杂性是可以接受的,因为“对于所有位(bit
)位置上的错误都是同样敏感的”。 在UDP
中,全0
表示省略了可选的检验和特性。另外一种表示:FFFF
,指示了0
的检验和。 (在IPv4
中,TCP
和ICMP
都强制性地规定了检验和,而在IPv6
中可以省略)。
相比于原码,反码能用加法替代减法运算了,但是需要额外的进位加法。并且也未解决两种零的表示问题。不过同样通过判断最高位是0还是1我们可以确定这个数是大于等于零还是小于等于零。
补码
补码通过一个简单的数学技巧解决了原码和反码方法的数学表示和运算问题。
正数:补码=原码
负数:补码=反码+1
例:得到十进制-1的补码
得到正数1(00000001)对应负数(-1)的反码,结果是11111110
反码加上1,结果是11111111
对于-2值进行相同处理,会得到11111110,-3会得到11111101。所以聪明的你可能会发现这里的规律。负数(8bit)的补码表示值从11111111开始递减,直到10000000(-128)。并且对于多字节整数长度,相同的规则跨字节适用。
补码回避了0有多种表示的问题以及加法计算时循环进位的需要。在补码中只有一个0(00000000
)。
从一个正数得到其对应负数的补码的简单方法表示如下:
操作 | 例1 | 例2 |
---|---|---|
1. 从右边开始,找到第一个’1′ | 0101001 |
0101100 |
2. 反转从这个’1’之后开始到最左边的所有位 | 1010111 |
1010100 |
对于相同的位数,补码格式表示的值的数量和无符号正数对应的值的数量是相同的,但是必须把值分为正值、负值和零。因此带符号正数的最大值是无符号值的一半。同时,因为零只有一个表示(00000000
),所以比原码和反码可以多表示一位负数的值,补码的表示范围为:[-2^(n-1),2^(n-1)-1]
。(这里为什么是负数多一个而不是负数多一个呢,原因是对比于原码和反码来说,+0
和正整数的表示是一样的,但是不再有-0
的表示,所以多一个负数值的表示)
补码以加法替代减法的操作与无符号数做加法一样,并且不需要额外的循环进位计算,所以这里就不再举例了。
至此,补码解决了无符号数无法表示负数的问题,也解决了原码和反码有两个零的表示问题,也解决了原码无法使用加法替换解法的问题,还解决了反码需要额外循环进位计算的问题。
关于模、互补、同余的论证这里不去讲,可以看一下参考链接。