我们都知道,在计算机的内部,所有的数据都是以二进制的形式存储的,为了区分正数和负数,在最高位拿出来当作符号位,0表示正数,1表示负数。而且,负数的存储,除了最高位的符号位,其他位使用的是补码的形式存储的。
计算一个数值的二进制补码,以下3个步骤:
- 求这个值的绝对值的二进制原码
- 原码取反,得到反码
- 反码加1,即是补码
例如,要求-18的二进制码,首先求18的二进制码:
1 | 0000 0000 0000 0000 0000 0000 0001 0010 |
求其反码:
1 | 1111 1111 1111 1111 1111 1111 1110 1101 |
最后反码加1
1 | 1111 1111 1111 1111 1111 1111 1110 1110 |
这样就求得了-18的二进制表示。
问题来了,为什么要采用补码的形式来表示负数呢?
答案是,为了方便计算。
为何这么说呢?下面咱们一步一步的来看这个问题。
为了简化这个问题,我们使用8位二进制表示整数。实际计算机使用32位表示。
那么,正整数 1 的二进制表示就是:
1 | 0000 0001 |
我们说过了,最高位为符号位,那么 -1 的二进制应该为如下形式:
1 | 1xxx xxxx |
对吧,最高位为1,表示负数。
如果除了符号位之外,其他位我们使用原码表示的话,-1 的二进制是不是应该如下:
1 | 1000 0001 |
那么,我们来计算一下 -1 + 1 的值:
1 | 1000 0001 |
换算成整数是多少,-2。这和正常的逻辑,-1+1=0相差甚远。为什么,除了符号位,正常位使用的是原码,那么-1和1是相同的,都是1,相加的结果是2,再加上符号位,-2.
从这里我们可以看出,除了符号位,负数的其他位断然不能使用原码表示。否则会出现计算逻辑错误。
开篇我们说过了,负数采用补码的形式,那么,补码这个定义怎么来的,为什么这么计算呢?
我们慢慢来看。-1 + 1 = 0
这个不必多说对吧,0的二进制是什么?
1 | 0000 0000 |
那么,-1 + 1 = 0
二进制是不是应该如下面的方式进行表示:
1 | 1xxx xxxx |
可能有人会问了,怎么可能,不管-1的二进制中x是多少,符号位 1 + 0 = 1
啊,算出来的永远是个负数啊???
是啊,但是如果发生进位了呢?我们采用8位表示一个整数,如果发生进位了,产生了9位,发生了溢出:
1 | 1 0000 0000 |
由于发生了溢出,最高位的1被舍弃,取低8位,是多少,0 啊,对不对。
那-1的二进制码是多少才能使计算发生进位,算一下啦,-1的二进制应该是:
1 | 1111 1111 //-1 |
好了,到这里我们知道了,-1的二进制表示为1111 1111
,那有什么规律吗,到底怎么算才能得到这个二进制表示,我们不能每次求一个负数的二进制的时候都用发生进位的二进制去减正数的二进制来获取啊?
是的,我们来找一下规律看看。
试想一下,什么时候会发生进位,发生溢出呢?
所有的位都变成了1, 再加1就进位发生溢出了呀。我们再来看一下上面的计算,
1 | 1111 1111 //-1 |
那么,对于一个二进制而言,怎么才能得到全部的位都是1呢?
反码啊,不管你二进制的位是0还是1,加上他的反码后得到的就是全部的位都是1,好了,再加1,就得到了0.
我们就把反码加1的这个值作为负数的二进制表示。我们把这种形式叫做补码,也就是上面我们从书里看到的,补码的计算,其反码加1.
采用补码,正负相加为0的计算能够表示清楚了,那么其他呢计算对不对呢,比如 -5 + 3,-4 + (-2)等的形式呢?
先来看看 -5 + 3
1 | -5的二进制: 1111 1011 |
哪个数的二进制是1111 1110
呢,首先看符号位是1,说明是一个负数,我们按照负数二进制补码的计算方式,逆运算回去
1 | 1111 1110 |
因此,得到的数是 0000 0010
,这个数的值是2.由于符号位为1,因此这里是-2.即-5+3=-2
如果都是负数呢,-4 + (-2)
1 | -4的二进制:1111 1100 |
这里超出8位,发生溢出,最高位舍弃,最终结果 1111 1010
符号位1,表示负数。逆运算回去:
1 | 1111 1010 |
获得的值为 2^1 + 2^2 = 6
由于符号位为1表示负数,最终结果是 -6
由此看来,负数采用补码的形式,是为了方便计算机内的运算。总的下来,也明白了补码是怎么来的,怎么运算的。