补码与符号位取反
补码与符号位取反
先来一个 C 语言的小例子:
#include <stdio.h>
#include <stdint.h>
int main(void)
{
int16_t n = -1;
n &= 0x7FFF; // 按位与
printf("%d", n); // 这里输出什么?
return 0;
}
对于16位的整数 n ,按位与运行将最高位设置为0(符号位),得到的结果却不是 1 ,结果是 32767 。
原因在于有符号整数的实现方式。
有符号整数,最容易想到的方式是在最高位加一个符号位,0表示整数,1表示负数,其它位不变(保留原始值),也即是原码方式。但这个方式有一个问题,存在两个0,正0和负0,在计算时需要先判断符号位,然后才能决定用加法还是减法,机器计算不便。
另外一个方法是负数全部按位取反,也就是反码方式。这个运算就相对简单了,进行加法时,按位计算,0和0为0,0为1为1,1和1为0并产生进位,最高位有进位时,结果要加1,减法可处理为其负数的加法。但还是有点问题,还存在两个0,正0和负0。
问题是出现负数上,那么把负数的反码 + 1 ,不就把负0去掉了吗?还真的是这样,而同时负数比整数能多表示一个数(这是基于同余的)。
严格的表达为:
对于位数为 n 的整数,其补码 [x]补 为:(2^n + x) mod 2^n ,
表示的范围为 -2^(n-1) <= n < 2^(n-1) ,注意正数最大为 2^(n-1)-1
即:
当 0 <= x < 2^(n-1) 时,
[x]补 = x 的原码
当 -2^(n-1) <= x < 0 时,
[x]补 = 2^n + x 的原码。
而经验上,可看作负数的补码为其反码加1(特殊数 -2^(n-1) ) 的反码 :
x < 0 时:
[x]补 = [x]反 + 1
特殊数 [ -2^(n-1) ] = 100...0
它的加法处理非常简单,符号位也可以运行,
[x+y]补 = (2^n + x + y) mod 2^n = ((2^n + x) + (2^n + y)) mod 2^n = [x]补 + [y]补
[x-y]补 = (2^n + x - y) mod 2^n = ... = [x]补 + [-y]补
现在回到原来的问题,对于16位 -1 ,其补码为:
[-1]补 = [-1]反 + 1 = 0xFFFFF
按位与去掉符号位,得到的是 0x7FFF 也就是16位整数最大的正数( 2^15 - 1) 32767。