整数的二进制底层表示
对于字长(word size)为w的机器,用w位bit表示一个字(word)。
无符号整数的二进制表示
如果该字表示一个无符号整数的话,我们可以用下列公式来计算
范围 : 0<=x<2w−1
有符号整数的二进制表示
如果该字表示一个有符号整数的话,现代计算机通常使用补码(two’s complement)方式的编码方式。
补码的确定规则基于这样的事实:
对于一个正整数x,它的相反数y,那么x+y=0是符合我们的直觉的。
对于[xw−1,xw−2.....x0]例如x=0000001想找另外的一个[yw−1,yw−2.....y0] y使得x+y=0000000(如果bitlen(x+y)==w+1截断最高位)那么y=1111111推导出y= x+1
因为x=0000001表示正整数的1,为了使得y=1111111表示-1,我们采用以下公式 :
我们给了最高位一个很大的负权重来达到我们的目的。
范围 : −2w−1<=x<=2w−1−1
这种编码方式成为补码。
使用 <stdints.h> 和<inttype.h>
由于不同机器的字长(字长影响数据范围)不同,c语言在不同字长的机器上int的大小可能略有区别,考虑到可移植性,我们应该尽量使用c99标准库的<stdints.h和<inttype.h>中的类型。
int main(void)
{
int16_t x0 = 10;
int32_t x1 = 10000;
int64_t x2 = 1000000000000;
printf("x0 : %d\n",x0);
printf("x1 : %d\n",x1);
printf("x2 : %" PRId64 "\n", x2);
return 0;
}
需要注意的是输出int64_t的时候使用PRId64这是<inttype.h>中的一个宏。
有符号整数和无符号整数之间的转换
c语言中一旦操作符某一边出现了unsigned int那么都会被自动转换为unsigned int这种转换是隐式的不宜察觉,很多bug由此出现。应当了解具体的细节。
转换的时候,bit sequence是不会改变的,只是编译器对它的解释方式发生变化。
有符号->无符号
看一个例子 :
int x = -50;//signed
unsigned y = 1;
if(x > y)
printf("ok\n");
程序输出为ok,这是因为if(x > y)这条语句,x是signed,y是unsigned,x会自动cast为unsiged,根据上边的公式可知,cast后必定大于1,所以会输出ok
无符号->有符号
看一个例子 :
unsigned x = (1 << 31);
int y = x;
printf("x = %ud , y = %d\n",x,y);
x = 2147483648d , y = -2147483648
套用上述公式即可解释
整数二进制表示形式的扩展和截断
不同字节的整数转换是一种常见的操作。
无符号整数的扩展
对于w字长的无符号整数[xw−1,xw−2.....x0]假设变为w+k位,那么很简单的就是高位补k个0变为[0,0,0,0,0..xw−1,xw−2.....x0],这种方式称为zero extension.
无符号整数的截断
假设我们不扩展无符号整数,由于某种特殊需求我们要截断它,对[xw−1,xw−2.....x0]w位比特串变为[xk−1,xk−2.....x0]k位(即去掉最高的w-k位),我们将会得到x mod 2k
证明 :
有符号整数的扩展
对于w字长的有符号整数[xw−1,xw−2.....x0]假设变为&w+k位,那么很简单的就是最高位重复k次(再加上原来的最高位一共有k+1个x(w−1),变为[x_{w-1},x_{w-1},x_{w-1},x_{w-1}x_{w-1},x_{w-2}…..x_{0}]$,这种方式称为sign extension.
下面我们证明 :
[xw−1,xw−2.....x0] ==[xw−1,xw−1,xw−1,xw−1xw−1,xw−2.....x0]
只需要证明:
[xw−1,xw−1,xw−2.....x0] =[xw−1,xw−2.....x0]即可
证明 :
[xw−1,xw−1,xw−2,…x0] =−xw−12w+∑w−1i=0xi2i=−xw−12w+xw−12w−1+∑w−2i=0xi2i=−xw−12w−1+∑w−2i=0xi2i=[xw−1,xw−1,xw−2,…x0]
有符号整数的截断
假设我们不扩展有符号整数,由于某种特殊需求我们要截断它,对[xw−1,xw−2.....x0]w位比特串变为[xk−1,xk−2.....x0]k位(即去掉最高的w-k位,去掉了最高w-k位那么符号位就被去掉了)
所以 :
(signed)[xw−1,xw−2,...x0]
我们采用以下公式 :
step 1 : res = x mod 2w
setp2 : res(unsigned) -> res(signed)
例如 : x=−7(1001),我们先将1001解释为unsigned得到9,截断一位即
上述k表示的是截断后的比特串的长度。
整数的运算
整数的加法
无符号整数的加法
对于w字长的无符号整数
我们可以用下列函数判断两个数相加是否溢出 :
int unsigned_add_ok(unsigned x,unsigned y)
{
unsigned sum = x + y;
return (sum >= x) ? 1 : 0;
}
有符号整数的加法
可以用下列函数判断两个有符号整数相加是否溢出:
int signed_add_ok(int x,int y)
{
int sum = x + y;
if( x < 0 && y < 0 && sum > 0 )
return 0;//negative overflow
else if(x > 0 && y > 0 && sum < 0)
return 0;//positive overflow
return 1;
}
整数取反(减法相当于加相反数)
无符号整数
基于公式(x+2w−x) mod 2w=2w mod 2w = 0
所以无符号整数取反将得到 :
有符号整数
这个略微有些特殊假如说w = 3,−2w−1表示为100那么只有再加上100才能得到0000表示0
整数乘法
无符号整数的乘法
对于w字长的无符号整数[xw−1,xw−2.....x0],表示的范围为0<=x<2w−1 ,那么x+y的范围为0<=x<22w−2w+1+1显然需要2w位比特串来保存,所以乘法会出现溢出的情况,实际上,这里机器内部仍然进行了取余运算,所以 :
可以用以下函数检验无符号整数相乘是否溢出:
int unsigned_multiply_ok(unsigned x,unsigned y)
{
unsigned product = x * y;
if(x != 0 && product / x != y)
return 0;
return 1;
}
有符号整数的乘法
类似有符号整数的截断运算(因为乘法很有可能会超出范围)。
我们首先将有符号整数的比特序列解释为无符号整数,然后无符号整数相乘遵循 mod 2k的法则,得到的结果再解释为有符号整数即可。
例 :
x=[101]=−3 解释为unsigned得到x=5
y=[011]=3 解释为unsigned得到y=3
所以 :
x∗y=3∗5=15(001111)mod23=7(111)解释为signed得到−1
可以用以下函数检验有符号整数相乘是否溢出
int signed_multiply_ok(int x,int y)
{
int product = x * y;
if(x != 0 && product / x != y)
return 0;
return 1;
}//同unsigned_multiply_ok
乘以常量
乘法需要多条机器指令,比较慢。一般来讲编译器会使用左移和加减法指令来替换掉乘法指令。
这很简单 :
x∗14=x∗(8+4+2)=(x<<3)+(x<<2)+(x<<1)
或者
x∗14=x∗(16−2)=(x<<4)−(x<<1)
x∗55=(64−8−1)=(x<<6)−(x<<3)−x
诸如此类。
除以2的幂
除法操作更加耗时,不过没有很好的优化方式。当除以2k这种形式的时候,编译器会将其优化为右移操作。
对于无符号整数,采用逻辑右移。
对于有符号整数,采用算术右移。
算术右移有些特殊,对于−12340我们看一个表:
| k | 算术右移k位 | Decimal | /2k |
|---|---|---|---|
| 0 | 1100111111001100 | -12340 | -12340.0 |
| 1 | 1110011111100110 | -6170 | -6170.0 |
| 4 | 1111110011111100 | -772 | -771.25 |
| 8 | 1111111111001111 | -49 | -48.203125 |
可以看出,单纯这样除是不精确的值,为了使得结果正确,使用下列公式:
406



被折叠的 条评论
为什么被折叠?



