结论概览:

  1. 在无1溢出的情况下,左移(或右移)运算等价于乘以(或除以)2y2^yyy为移动位数,且不使得原数完全移出);
  2. 在有1溢出的情况下,右移运算等价于整除。

一 移位运算符的含义

  移位运算符包含左移运算符<<和右移运算符>>。左移运算符是将一个二进制位的操作数按规定的移动位数向左移动,移出位舍弃,空位补0。右移运算符是将一个二进制位的操作数按规定的移动位数向右移动,移出位舍弃,正数补0,负数补1。它们是这样参与运算的:

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\mathbf{2}^\mathbf{x},移动位数为y(不使得原数完全移出),则权重更改的结果是2x±y\mathbf{2}^{\mathbf{x \pm y}}(左移为加,右移为减)

  例如上例中数值1的权重是222^2,移位后更改为22+22^{2+2}。对每一位都执行移位的运算(以左移为例),那么在无1溢出的情况下(因而该二进制数的最高位一定为0,所以这里有符号数和无符号数没有区别),对二进制数b15b14...b1b0b_{15}b_{14}...b_1b_0有:

b15b14...b1b0<<y=215b15+214b14+...+21b1+20b0<<y=215b15y+214b14y+...+2yb0+2y1×0+...+21×0+20×0=2y(215yb15y+214yb14y+...+21b1+20b0)=2yb15b14...b1b0\begin{aligned} b_{15}b_{14}...b_1b_0 << y & = 2^{15}b_{15} + 2^{14}b_{14} + ... + 2^1b_1 + 2^0b_0 << y\\ & = 2^{15}b_{15-y} + 2^{14}b_{14-y} + ... + 2^yb_0 + 2^{y-1}×0 + ... + 2^1×0 + 2^0×0\\ & = 2^y(2^{15-y}b_{15-y} + 2^{14-y}b_{14-y} + ... + 2^1b_1 + 2^0b_0)\\ & = 2^yb_{15}b_{14}...b_1b_0\\ \end{aligned}

  请注意,以上计算建立在无1溢出的情况下,亦即b15b14...b15y+1b_{15}b_{14}...b_{15-y+1}每一位都为0。

  因此,在无1\mathbf{1}溢出的情况下,左移(或右移)运算等价于乘以(或除以)2y2^yyy为移动位数,且不使得原数完全移出)。
  那么在有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); //结果是2 -3
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=31(-5)\div2=-3\cdots\cdots1。因此,该式整除结果是-3。

如果对负数的右移仍存疑,以下推算可能有帮助。
  以短整型(16位)为例。对二进制数b15b14...b1b0b_{15}b_{14}...b_1b_0(其中b15=1b_{15} = 1,且其余位不全为0),记其十进制值

D=(214b14+213b13+...+21b1+20b0)D = -(2^{14}b_{14} + 2^{13}b_{13} + ... + 2^1b_1 + 2^0b_0)

  那么它的补码的十进制值为

d=216(D)=216+D=216(214b14+213b13+...+21b1+20b0)d = 2^{16} - (-D) = 2^{16} + D = 2^{16} - (2^{14}b_{14} + 2^{13}b_{13} + ... + 2^1b_1 + 2^0b_0)

  记其二进制数为c15c14...c1c0c_{15}c_{14}...c_1c_0(其中c15=1c_{15} = 1,且其余位不全为1),则

d=(214c14+213c13+...+21c1+20c0)d = -(2^{14}c_{14} + 2^{13}c_{13} + ... + 2^1c_1 + 2^0c_0)

  设右移位数为yy(不使得原数完全移出),则右移后二进制数变为111...1c14c13...cy+1cy111...1c_{14}c_{13}...c_{y+1}c_y,其十进制值

d0=(214+213+...+214y+1+214yc14+213yc13+...+21cy+1+20cy)=(215215y+214yc14+213yc13+...+21cy+1+20cy)\begin{aligned} d_0 & = -(2^{14} + 2^{13} + ... + 2^{14-y+1} + 2^{14-y}c_{14} + 2^{13-y}c_{13} + ... + 2^1c_{y+1} + 2^0c_y)\\ & = -(2^{15} - 2^{15-y} + 2^{14-y}c_{14} + 2^{13-y}c_{13} + ... + 2^1c_{y+1} + 2^0c_y) \end{aligned}

  则移位后的原码的十进制值是

D0=216(d0)=216+d0=216(215215y+214yc14+213yc13+...+21cy+1+20cy)=216215+215y2y(214c14+213c13+...+2y+1cy+1+2ycy)=215+215y+2y(d+2y1cy1+2y2cy2+...+21c1+20c0)\begin{aligned} D_0 & = 2^{16} - (-d_0)\\ & = 2^{16} + d_0\\ & = 2^{16} - (2^{15} - 2^{15-y} + 2^{14-y}c_{14} + 2^{13-y}c_{13} + ... + 2^1c_{y+1} + 2^0c_y)\\ & = 2^{16} - 2^{15} + 2^{15-y} - 2^{-y}(2^{14}c_{14} + 2^{13}c_{13} + ... + 2^{y+1}c_{y+1} + 2^yc_y)\\ & = 2^{15} + 2^{15-y} + 2^{-y}(d + 2^{y-1}c_{y-1} + 2^{y-2}c_{y-2} + ... + 2^1c_1 + 2^0c_0) \end{aligned}

  如果将二进制数cy1cy2...c1c0c_{y-1}c_{y-2}...c_1c_0的十进制值记为d1d_1(注意该二进制的符号始终为负),那么有

d1=(2y1cy1+2y2cy2+...+21c1+20c0)d_1 = -(2^{y-1}c_{y-1} + 2^{y-2}c_{y-2} + ... + 2^1c_1 + 2^0c_0)

  因此

D0=215+215y+2y(d+d1)=215+215y+2y(216+D+d1)=216y+215+215y+2yD+2yd1\begin{aligned} D_0 & = 2^{15} + 2^{15-y} + 2^{-y}(d + d_1)\\ & = 2^{15} + 2^{15-y} + 2^{-y}(2^{16} + D + d_1)\\ & = 2^{16-y} + 2^{15} + 2^{15-y} + 2^{-y}D + 2^{-y}d_1 \end{aligned}

  那么

D=2y(D0216y215215y+2yd1)D = 2^y(D_0 - 2^{16-y} - 2^{15} - 2^{15-y} + 2^{-y}d_1)

  所以

DD0=2y+216215+y215+d1D0\frac{D}{D_0} = 2^y + \frac{-2^{16} - 2^{15+y} - 2^{15} + d_1}{D_0}

  根据计数可溢出的性质(是什么?),当d1d_1加或减2x2^xx>yx>y且为整数),其值不改变。因此得移位前后原码的关系

DD0=2y+d1D0\frac{D}{D_0} = 2^y + \frac{d_1}{D_0}

  亦即

D0=Dd12yD_0 = \frac{D - d_1}{2^y}

  其中,D0D_0是移出后原码,DD是移出前原码,d1d_1是移出的原码对应的补码,yy是右移位数,且其取值不使得原数完全移出。

检验这个结果

  • 对于-5 >> 1:-5的二进制数是1000 0000 0000 0101,右移1位后,移出的原码是1,对应的补码是1,那么得到移出后

5121=3\frac{-5-1}{2^1} = -3

  • 对于-4 >> 2:-4的二进制数是1000 0000 0000 0100,右移2位后,移出的原码是00,对应的补码是00,那么得到移出后

4022=1\frac{-4-0}{2^2} = -1

  • 对于-11 >> 3:-11的二进制数是1000 0000 0000 1011,右移3位后,移出的原码是011,对应的补码是101,那么得到移出后

11523=2\frac{-11-5}{2^3} = -2

通过尝试运行程序检验以上结果。