Skip to content

定点数的移位运算

2026 大纲 二(三)1 无符号整数的表示和运算 · 二(三)2 带符号整数的表示和运算。大纲没有为移位单列条目,它横跨这两条——同一串二进制位按无符号数移和按带符号数移结果不同,这就是逻辑移位与算术移位的全部分歧所在。

移位等于乘除 2k,但这个等价有两处会失效

十进制里 15 m 写成 1500 cm,数字没变,只是相对小数点左移了两位并补了两个 0——左移两位就是乘 102。二进制同理,只是基数换成 2;而计算机中小数点的位置是事先约定的、不占任何一位,所以机器数相对这个固定小数点左移 n 位就是乘 2n、右移 n 位就是除 2n

  • 左移一位 乘 2(未溢出时)
  • 右移一位 除以 2(忽略移出的低位)

没有乘除电路的机器,正是靠"移位 + 加"组合出乘除运算的——乘法器和除法器的整个设计都从这里开始,见 乘除运算的基本原理与实现结构

但括号里那两个限定不是可有可无的:

🔴 等价失效的两处:① 左移可能溢出——移出去的高位一旦是有效位,结果就错了,而不是"精度差一点";② 右移一定丢精度,而且对负数还会丢得"偏一边"(第四节会看到它是向下取整而不是向零截断)。

本篇后面四节就是在处理这两处失效:补什么位、怎么判溢出、负数右移差在哪、以及不丢位的那种移位是干什么用的。

二、补 0 还是补 1

移位后总有一端空出若干位,补什么取决于这串二进制位被当成什么数

🔴 逻辑移位把操作数看作无符号整数,两端一视同仁一律补 0算术移位把操作数看作带符号数,铁律是符号位不变,所以左移补 0、右移补符号位

🔴 看似四条其实只有两条:左移一律补 0;右移补的是符号位的值——正数的符号位就是 0,所以"正数右移补 0"是同一条规则的特例,不必单独记。

算术右移之所以要补符号位,是因为补码的符号位携带的是权重 2n1,右移一位后这个权重要变成 2n2,只有把符号位复制一份填进来,才能让新的最高两位共同表示原来那个负权重;补 0 会让一个负数瞬间变成正数。

⚠️ "补符号位"只对补码成立。 教材列的三种码制补位规则是:正数在原码 / 补码 / 反码下均补 0;负数则原码补 0、补码左移补 0 右移补 1、反码补 1。408 的运算一律用补码,掌握补码那一栏即可。

移位方向正数(符号位 0)负数(符号位 1)
左移低位补 0低位补 0
右移高位补 0高位补 1
4 位无符号数与 4 位补码上的逐例对照(想确认溢出判据怎么用、或想看"补错位数值会变成什么"时展开)

4 位无符号数(逻辑移位):

原始值操作结果说明
0001(1)左移 1 位0010(2)1×2=2
0001(1)右移 1 位0000(0)1÷2 取整为 0
1000(8)左移 1 位0000(0)移出的高位是 1,溢出
1011(11)右移 1 位0101(5)11÷2 取整为 5

4 位补码(算术移位):

原始值真值操作结果真值说明
0010+2左移 10100+4正常
0100+4左移 110008溢出(符号位被改)
10017左移 10010+2溢出14 超出范围)
0110+6右移 10011+3高位补 0
10017右移 111004高位补 1
11013右移 111102高位补 1

同一串位在两种解释下的右移对照:

位串逻辑右移 1算术右移 1
011000110011(相同)
100101001100(不同)

三、算术移位与逻辑移位的分界

对比项算术移位逻辑移位
操作数被当作带符号数(补码)无符号数
左移补位补 0补 0
右移补位符号位0
溢出判据移出位与新符号位不同移出位为 1
x86 指令sal / sarshl / shr

这张表有三处值得单独拎出来:

🔴 两种溢出判据不一样。逻辑左移:移出的那一位是 1 就溢出;算术左移:移出的那一位(即原符号位)与移位后的新符号位不同就溢出。比较的对象是"移出的位"和"新符号位"——拿新符号位跟自己比、或跟原数的其他位比,都得不出结论。

🔴 左移时算术与逻辑的结果完全相同,因为两者都是低位补 0、位串一模一样。这正是 x86 里 salshl 是同一条指令两个助记符的原因。分歧只在右移sar / shr),所以 C 语言里 << 对有符号和无符号生成同一条指令、而 >> 不是。

⚠️ 同一次移位,在两种解释下的溢出结论可以不一样:逻辑左移当成无符号看(移出 1 就超范围),算术左移当成带符号看(符号变了就出错)。右移的结果则在最高位是 0 时相同(算术右移补的符号位恰好是 0),只有最高位为 1 时才分道扬镳。

数据在不同宽度类型之间转换时的位级行为,见 C 语言中的类型转换

四、补码算术右移是向下取整

这是本节最容易被想当然的一处。7 的 4 位补码 1001 算术右移一位得 1100 =4,但按 C 语言的整数除法 7/2=3。两者差 1,而且不是偶然——只要被除数为负且除不尽,就一定差 1:右移丢弃低位等价于向数轴负方向取整x),而 C 语言规定的整数除法是向零截断

3.5=4trunc(3.5)=3

要用右移实现带符号数除以 2k,必须先加一个偏移量把它掰回来:

x/2k=(x+2k1)k(x<0)

x=7k=1 验证:(7+1)1=61=3

这也解释了一个常见现象:编译器把 x / 2 编译成移位时,如果 x 是有符号类型,生成的从来不是一条 sar,而是先做符号判断加偏移、再移位的三四条指令;只有 unsigned 类型才会退化成一条 shr

五、循环移位:不丢位的那一种

移出的位不丢弃,而是填回另一端。

操作规则
不带进位循环左移最高位移到最低位,同时送入 CF
不带进位循环右移最低位移到最高位,同时送入 CF
带进位循环左移最高位 → CF → 最低位(CF 参与循环)
带进位循环右移最低位 → CF → 最高位(CF 参与循环)

8 位不带进位循环左移:1011001101100111,最高位的 1 回到了最低位。带进位循环移位把 CF 串进移位链,可以把一个长数拆成若干机器字逐段移位——这是它在多字长数据上的典型用处。

🔴 循环移位不丢位,也不做乘除。 移出的位填回另一端,只改变位的排列、不改变"位的集合",所以它与原数没有任何简单的乘除关系。它的用途是三类:高低字节对调(大小端转换)、在多字节数据之间传递进位、以及位域的提取与拼接。

⚠️ 最后提一条贯穿全篇的前提:按什么宽度移,由类型宽度决定,不由数值决定1011 在 4 位里既是 5(补码)也是 11(无符号),右移一位分别得 11013)和 01015)。位串本身不携带"我是有符号还是无符号"的信息,这个信息只存在于指令里——选错指令不会报错,只会静默算出另一个数。

考点速记

  1. 机器中小数点位置是约定的,因此左移 n 位就是 ×2n、右移 n 位就是 ÷2n;但左移可能溢出、右移一定丢精度
  2. 补 0 还是补 1 取决于这串位被解释成无符号数还是带符号数——左移一律补 0(所以 salshl 是同一条指令),右移补的是符号位的值,分歧只在右移;两种解释下的溢出判据也不同。
  3. 补码算术右移是向下取整,与 C 语言除法的向零截断在负数上必然差 1,用右移代替带符号除法必须先加偏移量;循环移位不丢位,用于字节对调与多字长间传递进位,不用于乘除。

这一节在真题里被考过的形式(下方「真题练习」里属于本篇的那几道):

  • 给一个机器数,问分别做逻辑右移和算术右移得到什么:这是最直接的一道。逻辑右移高位补 0,算术右移高位补符号位——最高位是 1 时两者结果不同,是 0 时相同。
  • 给一个机器数,问连做两次移位(如先算术右移 3 位再算术左移 2 位)的最终结果逐步做,不要合并。算术右移补符号位、算术左移补 0,两次补的位不一样,合并成"净右移 1 位"会算错。
  • 给两个机器数,问 2x+y/2 这类表达式的结果×2 是左移一位、/2 是右移一位,都按算术移位做(操作数是带符号整数),算完再判有没有溢出。⚠️ 中间某一步溢出时整个结果就是"溢出",不能接着往下算。

易错:算术右移补 0。它补的是符号位。

易错:把连续两次移位合并成一次净移位。两次补的位不同,必须逐步做。

易错:用算术右移直接代替带符号除法。它是向下取整,负数上与 C 的除法差 1。

易错:判算术左移溢出时拿错比较对象。要比的是"移出的那一位"与"移位后的新符号位"。

教材出处
  • 移位与乘除 2n 的等价关系、"没有乘(除)法运算线路时可用移位和加法实现乘(除)运算":唐朔飞《计算机组成原理》第 3 版 §6.2 定点运算,印刷页 p234
  • 三种码制的算术移位补位规则表、"不论正数还是负数,移位后符号位均不变":同书印刷页 p234–p236
  • 算术移位与逻辑移位的区别(含 0101001110110010 的对照示例):同书印刷页 p236
  • 用右移实现带符号数除法须先加偏移量:袁春风《计算机组成与系统结构》第 3 版第 3 章,印刷页 p80

相关知识

乘除运算的基本原理与实现结构C 语言中的类型转换真值与机器数:四种编码的定义

真题练习