1的补码及2的补码
一、计算机的负数表示
二、1的补码One\’s complement——反码
三、2的补码Two\’s complement——补码
一、计算机的负数表示
数据在计算机中由一个一个的0,1比特表示,所以在表示负数的时候,不能直接添加符号\’-\’来表示这是个负数,必须采用一些规范或者约定来区分正数和负数。
有四种比较有名的表示负数的方法:原码(sign-and-magnitude),1的补码(one\’s complement),2的补码(two\’s complement),以及excess-k(即Offset binary)。
二、1的补码One\’s complement——反码
1、特性
1的补码比较简单,它在表示负数的时候简单把该负数的绝对值(即相应的正数)的二进制位全部反转。比如1用0000 0001表示,那么-1就用1111 1110表示。
比较有意思的是由于+0用0000 0000表示,反转后1111 1111就表示-0。所以在1的补码里有两个零。
Binary value |
Ones\’ complement interpretation |
Unsigned interpretation |
00000000 |
+0 |
0 |
00000001 |
1 |
1 |
⋮ |
⋮ |
⋮ |
01111101 |
125 |
125 |
01111110 |
126 |
126 |
01111111 |
127 |
127 |
10000000 |
−127 |
128 |
10000001 |
−126 |
129 |
10000010 |
−125 |
130 |
⋮ |
⋮ |
⋮ |
11111101 |
−2 |
253 |
11111110 |
−1 |
254 |
11111111 |
−0 |
255 |
From <http://en.wikipedia.org/wiki/Signed_number_representations#Ones.27_complement>
以上表举例来看:(-1)+1 = 0,对应的1的补码加法
0000 0001 ——-1
+1111 1110 ——1
=========
1111 1111——-0
由于负数都是由相应的正数二进制位全反转得到的,所以这里有个特性就是把它们相加的话会得到一个二进制位为全1的数,在1的补码的表示方法中,全1表示-0。这也是为什么这种表示方法叫做”1的补码”。1的补码在IP,TCP,UDP,ICMP等协议头中应用于计算校验和。
还有一点很有意思的是,在1的补码表示的数中,所有的正数最高比特都是0,而所有的负数最高比特都是1。而且它的正最大值是127,没有向上加过去表示128。在我们平常来看,最高位表示符号位0代表正,1代表负是极为正常的事。但是在1的补码里,没有设置符号位,之所以造成这样的”巧合”,存在着一定的必然性。我们假设它能表示到128,即1000 0000,那么-128就表示成0111 1111,可是0111 1111已经用来表示127了,这就造成了冲突。其实问题很简单,我们可以把最高位的0,1看作是前缀码,用这位前缀码保证对正数的反转操作不存在冲突。这也是为什么正数最大只能表示到127。
更有意思的是,国内将1的补码翻译成反码,其求负数的反码时,”保留符号位,将其余的比特进行反转”。但是这样就不能解释为什么有-0的存在。更不能解释在IP,TCP,UDP,ICMP协议头里求校验和时所谓的”反码求和”是怎么回事。
想象有一根数轴,两端分别向正无穷和负无穷延伸,从中间0的位置一刀劈下去(0有两个),分成非负和非正的两半。
这根轴看起来很对称。
如果我们从+0开始数数,即从0000 0000开始不断加1(0000 0001,0000 0002,0000 0003…),我们会发现我们不停在循环(\’0\’-\’127\’,\’-127\’-\’0\’)。原因是当我们数到127(0111 1111)时,再加1变成-127(1000 0000)。这个时候看起来是这样的:
2、1的补码的加法
1的补码的加法跟其它的加法是一样的,只是它有个循环进位(end-around carry)概念。它要求把最后结果的进位加回到最后的结果中去。
比如计算(-1)+2:
1111 1110
+0000 0010
=========
(1)0000 0000
+ 1 进位
=========
0000 0001
为什么要这样呢?
我们考虑在①+127处,我们加1变成-127,在-127处我们再减1,同样会出错。在应用的时候我们要注意这种超出范围的错误。②处,由于我们有两个零,在做加法时,我们要加回一个0去。比如还是计算(-1)+2,
我们拿上面的圈来想象计算的过程,绕着圈顺时针走步,圈上的每一个数字对应一个走步,计算(-1)+2,也即在(-1)处走两步,我们走到+0处。由于正常的运算中没有两个0,我们在最后回加一个1。
由于溢出,得到的结果等于0(0000 0000),就是因为中间-0占用了一个计数,我们把进位1加最后结果上,得到正确结果1:
1111 1110
+0000 0010
=========
(1)0000 0000
+ 1
=========
0000 0001
减法可以用加法来表示。
三、2的补码Two\’s complement——补码
1、从1的补码的角度来理解补码加法
负数的补码经常用反码加一来计算。将补码与1的补码加法联系起来可以理解为什么可以用补码加法代替减法。
将1的补码中-0的编码空出来给-1用,后续数值依次前移,最后1000 0000会空出来,我们给它分配-128。因此,我们可以取值[-1,-128]U[0,127]。
因此,补码根据反码(即前文1的补码)加一来计算其实就是把-0挤掉,留了个位置给-128。
Binary value |
Two\’s complement interpretation |
Unsigned interpretation |
00000000 |
0 |
0 |
00000001 |
1 |
1 |
⋮ |
⋮ |
⋮ |
01111110 |
126 |
126 |
01111111 |
127 |
127 |
10000000 |
−128 |
128 |
10000001 |
−127 |
129 |
10000010 |
−126 |
130 |
⋮ |
⋮ |
⋮ |
11111110 |
−2 |
254 |
11111111 |
−1 |
255 |
From <http://en.wikipedia.org/wiki/Signed_number_representations#Ones.27_complement>
那为什么可以用补码来计算减法呢?理解了反码的加法,其实就不难理解为什么补码可以直接用来计算减法了。
2、快速写出补码
正数的补码是它本身。
负数的补码:
从右往左看,从第一个1出现的位置的下一位开始,反转比特,符号位不反转。比如(11010110) 的补码为10101010。
参考资料
【1】http://en.wikipedia.org/wiki/Signed_number_representations#Sign-and-magnitude_method