原码、反码和补码——整数运算
下文以8位整数为例,第一位表示符号位,0为正,1为负
编码说明
所有整数的原码、反码、补码都相同。负数的反码和补码的符号位都不改变,反码其它位求反,补码其它位求反后再加1。同一个二进制数在不同编码中的解释可能不同,一般来说,0开头的8位数据在三种编码中解释相同,而以1开头的则解释不同
[+1]原=0000 0001,[+1]反=0000 0001,[+1]补=0000 0001
[-1]原=1000 0001,[-1]反=1111 1110,[-1]补=1111 1111
原码表示的范围是[-127~-0,+0~+127],反码的范围和原码一致,补码的范围是[-128~0~+127],不同之处在于 1000 0000 在原码中是-0,而在补码中规定是-128,
[-127]补+[-1]补=[1000 0001]补+[1111 1111]补=[1000 0000]补
[+127]补+[+1]补=[0111 1111]补+[0000 0001]补=[1000 0000]补
符号位是1,所以就规定成-128。[0]补=[+1]补+[-1]补=[0000 0001]补+[1111 1111]补=[0000 0000]补。
整数运算的问题
所有整数运算全部可以化为加法,包括正正加,正负加,负负加,用原码计算,正正结果一般是正确的,但是会有溢出,负负会把符号位加成0,而正负则结果根本就不对,例如
[+2]原+[-1]原= 0000 0010+1000 0001 = 1000 0011 = [-3]原
结果错误了,原因是符域和值域是分开计算的,要想一个办法让他们一起参与运算。
补码的来历
我们考虑这样一种运算,f(x)= x mod 2^n,这个运算我们总能保证f(x)的值不超过2^n,特别对于x如果是二进制数,这个运算会非常简单,我们只要保留x的最低n位即可,函数f就可以理解为取x的最低n位,加法运算可以保证封闭性,利用这个式子
f(x+y)=f(f(x)+f(y))
我们现在需要确定的是,给定x>0,f(-x)=?,就是负数的映射方式,根据f定义容易知道f(-x)=-f(x),而f(x)-f(x)=0,所以f(x)+f(-x)=0,上文我们强调我们所有的加法最终结果都要求f,即保证结果不超过2^n,所以f(x)+f(-x)=2^n也满足我们的要求(和的最高位1被扔掉了),给定f(x),求f(-x)的方法实际就是,对f(x)按位求反然后加1,而我们发现这个就是求补码的方法。
下面来看符号位如何参与计算,设x,y>0,8位二进制数,7位值域,1位符号域,n=7,f(x)-f(y)=f(f(x)+2^n-f(y)),如果f(x)>f(y),那么这个差应该是正数,而2^n正好和负数的符号位1相抵消,使得结果是负的;如果f(x)<f(y),那么要从2^n上扣除这个差值,所以结果一定小于2^n,不会影响到符号位的1,而结果也正好是负数。
这种运算背后蕴含的数学道理和同余有关,具体可以参考这两篇帖子《原码, 反码, 补码 详解》,《[原创] 原码,补码和反码》
另外,补码运算中,正正和负负会出现溢出情况,而正负则一定不会出现,对于溢出的情况可以用双符号位表示溢出情况,01表示两正数相加“上溢”,10表示两负数相加“下溢”,具体可以参考http://www.dz3w.com/info/digital/0081771.html
第一篇总结日志,还请大家多指出错误。