结论概览:
- 在无1溢出的情况下,左移(或右移)运算等价于乘以(或除以)2y(y为移动位数,且不使得原数完全移出);
- 在有1溢出的情况下,右移运算等价于整除。
一 移位运算符的含义
移位运算符包含左移运算符<<和右移运算符>>。左移运算符是将一个二进制位的操作数按规定的移动位数向左移动,移出位舍弃,空位补0。右移运算符是将一个二进制位的操作数按规定的移动位数向右移动,移出位舍弃,正数补0,负数补1。它们是这样参与运算的:
例如:
1 2 3 4 5 6 7 8
| #include <stdio.h>
int main() { short a = 4; a <<= 2; return 0; }
|
变量a被赋值4,换算为二进制数即0000 0000 0000 0100,左移2位后即0000 0000 0001 0000。
二 移位运算符的实质
移位运算符实际上更改了数值对应的权重。在无1溢出的情况下,设原来的权重是2x,移动位数为y(不使得原数完全移出),则权重更改的结果是2x±y(左移为加,右移为减)。
例如上例中数值1的权重是22,移位后更改为22+2。对每一位都执行移位的运算(以左移为例),那么在无1溢出的情况下(因而该二进制数的最高位一定为0,所以这里有符号数和无符号数没有区别),对二进制数b15b14...b1b0有:
b15b14...b1b0<<y=215b15+214b14+...+21b1+20b0<<y=215b15−y+214b14−y+...+2yb0+2y−1×0+...+21×0+20×0=2y(215−yb15−y+214−yb14−y+...+21b1+20b0)=2yb15b14...b1b0
请注意,以上计算建立在无1溢出的情况下,亦即b15b14...b15−y+1每一位都为0。
因此,在无1溢出的情况下,左移(或右移)运算等价于乘以(或除以)2y(y为移动位数,且不使得原数完全移出)。
那么在有1溢出的情况下呢?因为左移运算结果具有个体差异性(如正负不确定),因此这里不予讨论。而对于右移运算,它的运算结果的符号总是不变,我们不妨对其进行以下测试:
1 2 3 4 5 6 7 8 9 10 11
| #include <stdio.h>
int main() { short b = 5; b >>= 1; short c = -5; c >>= 1; printf("%hd\n%hd\n", b, c); return 0; }
|
变量b被赋值5,换算为二进制数,即0000 0000 0000 0101,右移1位后,末位的1被舍弃,即0000 0000 0000 0010,亦即2。换而言之,在除以2以后,右移运算使得不足2的部分被舍弃,也就是余数被舍弃。因此,对于非负数而言,右移运算等价于整除。
变量c被赋值-5,换算为二进制数,即1000 0000 0000 0101,负数在内存中以补码的形式存储(为什么?),即1111 1111 1111 1011,右移1位后,末位的1被舍弃,即1111 1111 1111 1101,其原码为1000 0000 0000 0011,即-3。因此,对于负数而言,右移运算也等价于整除。
请注意,对于需要求余数的除法而言,总是需要保证余数是正数,例如(−5)÷2=−3⋯⋯1。因此,该式整除结果是-3。
如果对负数的右移仍存疑,以下推算可能有帮助。
以短整型(16位)为例。对二进制数b15b14...b1b0(其中b15=1,且其余位不全为0),记其十进制值
D=−(214b14+213b13+...+21b1+20b0)
那么它的补码的十进制值为
d=216−(−D)=216+D=216−(214b14+213b13+...+21b1+20b0)
记其二进制数为c15c14...c1c0(其中c15=1,且其余位不全为1),则
d=−(214c14+213c13+...+21c1+20c0)
设右移位数为y(不使得原数完全移出),则右移后二进制数变为111...1c14c13...cy+1cy,其十进制值
d0=−(214+213+...+214−y+1+214−yc14+213−yc13+...+21cy+1+20cy)=−(215−215−y+214−yc14+213−yc13+...+21cy+1+20cy)
则移位后的原码的十进制值是
D0=216−(−d0)=216+d0=216−(215−215−y+214−yc14+213−yc13+...+21cy+1+20cy)=216−215+215−y−2−y(214c14+213c13+...+2y+1cy+1+2ycy)=215+215−y+2−y(d+2y−1cy−1+2y−2cy−2+...+21c1+20c0)
如果将二进制数cy−1cy−2...c1c0的十进制值记为d1(注意该二进制的符号始终为负),那么有
d1=−(2y−1cy−1+2y−2cy−2+...+21c1+20c0)
因此
D0=215+215−y+2−y(d+d1)=215+215−y+2−y(216+D+d1)=216−y+215+215−y+2−yD+2−yd1
那么
D=2y(D0−216−y−215−215−y+2−yd1)
所以
D0D=2y+D0−216−215+y−215+d1
根据计数可溢出的性质(是什么?),当d1加或减2x(x>y且为整数),其值不改变。因此得移位前后原码的关系
D0D=2y+D0d1
亦即
D0=2yD−d1
其中,D0是移出后原码,D是移出前原码,d1是移出的原码对应的补码,y是右移位数,且其取值不使得原数完全移出。
检验这个结果
- 对于
-5 >> 1:-5的二进制数是1000 0000 0000 0101,右移1位后,移出的原码是1,对应的补码是1,那么得到移出后
21−5−1=−3
- 对于
-4 >> 2:-4的二进制数是1000 0000 0000 0100,右移2位后,移出的原码是00,对应的补码是00,那么得到移出后
22−4−0=−1
- 对于
-11 >> 3:-11的二进制数是1000 0000 0000 1011,右移3位后,移出的原码是011,对应的补码是101,那么得到移出后
23−11−5=−2
通过尝试运行程序检验以上结果。