本文基于一定的前提,譬如:您已经知道了计算机是01二进制世界,无符号数主要用于逻辑运算,而有符号数用于算术运算。
有符号数有:原码,补码,反码,移码等表示。
原码便于人类阅读和计算,但是不利于计算机进行运算,因此有了补码。
那么补码为什么便于运算呢?
其实从补码定义的公式时很方便就推导出这个结论的,但是公式背后的含义呢?由于需要分的场景比较多,有9种,此处以一正一负展开讨论。由于随性而写,并没有很好地展示出补码的意义,有兴趣的读者可以再推究到其他场景体悟总结。
首先我们要明白,原码的计算不便主要是在异号的时候,同号其实是很方便的。如 1001 0001 + 1100 0101 = 1101 0110 (符号位不参与计算,其余位直接相加即可。若有到符号位的进位就是溢出了,这又是另外一个问题)。不方便的地方是异号计算,也就是两个正数相减是很难的。它的步骤如下:
- 比较A,B绝对值大小,即抛开最高位符号位不看,剩下的数值谁更大
- 结果的符号位按绝对值更大方来取
- 大的绝对值减去小的绝对值,获得的正数为最终的绝对值
如 17 - 69 = 0001 0001 + 1100 0101 = 1011 0100 = -52
显然,在原码中,减法运算足足比加法运算多了两个步骤,都是为了符号做判断的,这太浪费了。
如果以原码加法作为最基础的运算,我们可以尝试解决这个问题——为正负号判断而多做的两步能否在相加的时候一起解决?
答案当然是肯定的,这就是补码应运而生的原因。我们来看看补码的规则:
- 原码为正时,补码不变;
- 原码为负时,原码的符号位不变,其余位按位取反,末位加一得到补码。此处应当进一步解读,补码的数值位其实是分为高低位的,而这个高低位的划分方式是根据负数时最后一个1的位置来的。例如 1001 0001 则高位为1001 0000,低位为0001;0111 1100的高位为0111 1000,低位为0100 。按高低位区分后,设X为原码,X补 = X高反 + X低。而X + X反 = 2^n - 1(n为符号位,譬如八位有符号数 0000 0001 与其反码 0000 1110 相加为 0111 1111 = 2^7 - 1,而符号位就是7)。因此:X反 = 2^n - 1 - X,这个结论将是补码优化减法的关键。
既然我们希望补码无论是加法还是减法,都能够直接相加得出结果,那么补码的加法其实和原码加法是一样的。因此正数的时候与源码保持一致就可以解决了。
而减法就是正数A+负数B,在原码中需要比对|A|,|B|大小并将绝对值大的符号位设为结果的符号位。而反码中就是一步到位,那么
- 符号位参与计算,显然在发生进位前,符号位一定是1,对于原码来说,相当于先将结果假设为负的。
- 如果数值位的加法发生了进位,则符号位变号,结果变成了正数;如果数值位不进位,则结果维持负数。
- 根据前文补码的规则中提到,补码与原码关于高低位的对应关系,以及反码与原码的等式。可以得到补码的计算原理:
设A,B为两个绝对值(也就是抛出符号位的数值),由于B与-B只是符号位相反,因此为了方便书写,将[-B]高简记作B高,将[-B]补记作B补。A高的位数选择由B高决定(因为我们已经约定了补码的高低位由负数决定,正数是不存在这一特征的。)
由于A高 ≤ 2^n - 1 ,B高 >= 0,因此A高 + B高 = A高 - B高 ≤ 2^n - 1。(此处n为A高的位数,而非整个A的位数)也就是说,
1.A高 < B高 => A高 + x= B高,
2.A高 = B高,
3.A高 > B高 => A高 = B高 + x。
1 ≤ x ≤ 2^n - 1
下面我们先研究补码运算的符号正确性:
1.则当A高 > B高 => A高 = B高 + x 的时候,A补高 + B补高 = A高 + B高反 = A高 + 2^n - 1 - B高 = 2^n + x - 1 ∈ [2^n,2*(2^n - 1)],余数为2^n则符号位需要进一位,从1变为0,2*(2^n - 1) < 2*2^n ,因此只够进一位,不足以进位到符号位之前去。因此在补码运算中,若正数的绝对值大于负数的绝对值,相加后结果也为正。
eg:A = 69 = 0100 0101,B = 17 = 0001 0001,-B = 1001 0001,记B补 = [-B]补 = 1110 1111(其实这是错的,此处仅为书写方便,因为B为正数,B补高 = B高,这个符号用不上)。
+A高 = 10 0010,+B高 = 00 1000,x = 01 1010,A补高 - B补高 = A高 - B高反 = 010 0010 + 111 0110 = 001 1000。结果C的高位C高已经变成正号。(最终计算的时候要带上符号位)
2.A高 = B高时,x = 0,因此:A补高 + B补高 = A高 + B高反 = B高 + B高反 = 2^n - 1,也就是1...1(有n个1)。显然,若低位进位则为正,否则为负。而低位的结构为10...0(m个0,且m+n为B的位数),因此低位是否进位完全取决于A的低位是否为1开头。显然若A低0开头则A < B,不进位,那么A - B为负。若A低1开头,则A ≥ B,进位,A - B 为正。注意,[+0]补 = [-0]补 = 0...0 。因此结果的正负号正确。
3.A高 < B高,显然与1是完全一样的道理,不再赘述。
现在来看计算值的正确性:
先看补码定义中X为负时,[X]补 = 2^n - |X|。
因此,C补 = A补 + B补 = A + 2^n - |B|,|C| = 2^n - C补 = 2^n - A - 2^n + |B| = |B| - A.