Appearance
定点数的移位运算
2026 大纲 二(三)1 无符号整数的表示和运算 · 二(三)2 带符号整数的表示和运算。大纲没有为移位单列条目,它横跨这两条——同一串二进制位按无符号数移和按带符号数移结果不同,这就是逻辑移位与算术移位的全部分歧所在。
移位等于乘除 ,但这个等价有两处会失效
十进制里 15 m 写成 1500 cm,数字没变,只是相对小数点左移了两位并补了两个 0——左移两位就是乘
- 左移一位
乘 2(未溢出时) - 右移一位
除以 2(忽略移出的低位)
没有乘除电路的机器,正是靠"移位
但括号里那两个限定不是可有可无的:
🔴 等价失效的两处:① 左移可能溢出——移出去的高位一旦是有效位,结果就错了,而不是"精度差一点";② 右移一定丢精度,而且对负数还会丢得"偏一边"(第四节会看到它是向下取整而不是向零截断)。
本篇后面四节就是在处理这两处失效:补什么位、怎么判溢出、负数右移差在哪、以及不丢位的那种移位是干什么用的。
二、补 0 还是补 1
移位后总有一端空出若干位,补什么取决于这串二进制位被当成什么数:
🔴 逻辑移位把操作数看作无符号整数,两端一视同仁一律补 0;算术移位把操作数看作带符号数,铁律是符号位不变,所以左移补 0、右移补符号位。
🔴 看似四条其实只有两条:左移一律补 0;右移补的是符号位的值——正数的符号位就是 0,所以"正数右移补 0"是同一条规则的特例,不必单独记。
算术右移之所以要补符号位,是因为补码的符号位携带的是权重
⚠️ "补符号位"只对补码成立。 教材列的三种码制补位规则是:正数在原码 / 补码 / 反码下均补 0;负数则原码补 0、补码左移补 0 右移补 1、反码补 1。408 的运算一律用补码,掌握补码那一栏即可。
| 移位方向 | 正数(符号位 0) | 负数(符号位 1) |
|---|---|---|
| 左移 | 低位补 0 | 低位补 0 |
| 右移 | 高位补 0 | 高位补 1 |
4 位无符号数与 4 位补码上的逐例对照(想确认溢出判据怎么用、或想看"补错位数值会变成什么"时展开)
4 位无符号数(逻辑移位):
| 原始值 | 操作 | 结果 | 说明 |
|---|---|---|---|
0001(1) | 左移 1 位 | 0010(2) | |
0001(1) | 右移 1 位 | 0000(0) | |
1000(8) | 左移 1 位 | 0000(0) | 移出的高位是 1,溢出 |
1011(11) | 右移 1 位 | 0101(5) |
4 位补码(算术移位):
| 原始值 | 真值 | 操作 | 结果 | 真值 | 说明 |
|---|---|---|---|---|---|
0010 | 左移 1 | 0100 | 正常 | ||
0100 | 左移 1 | 1000 | 溢出(符号位被改) | ||
1001 | 左移 1 | 0010 | 溢出( | ||
0110 | 右移 1 | 0011 | 高位补 0 | ||
1001 | 右移 1 | 1100 | 高位补 1 | ||
1101 | 右移 1 | 1110 | 高位补 1 |
同一串位在两种解释下的右移对照:
| 位串 | 逻辑右移 1 | 算术右移 1 |
|---|---|---|
0110 | 0011 | 0011(相同) |
1001 | 0100 | 1100(不同) |
三、算术移位与逻辑移位的分界
| 对比项 | 算术移位 | 逻辑移位 |
|---|---|---|
| 操作数被当作 | 带符号数(补码) | 无符号数 |
| 左移补位 | 补 0 | 补 0 |
| 右移补位 | 补符号位 | 补 0 |
| 溢出判据 | 移出位与新符号位不同 | 移出位为 1 |
| x86 指令 | sal / sar | shl / shr |
这张表有三处值得单独拎出来:
🔴 两种溢出判据不一样。逻辑左移:移出的那一位是 1 就溢出;算术左移:移出的那一位(即原符号位)与移位后的新符号位不同就溢出。比较的对象是"移出的位"和"新符号位"——拿新符号位跟自己比、或跟原数的其他位比,都得不出结论。
🔴 左移时算术与逻辑的结果完全相同,因为两者都是低位补 0、位串一模一样。这正是 x86 里
sal与shl是同一条指令两个助记符的原因。分歧只在右移(sar/shr),所以 C 语言里<<对有符号和无符号生成同一条指令、而>>不是。
⚠️ 同一次移位,在两种解释下的溢出结论可以不一样:逻辑左移当成无符号看(移出 1 就超范围),算术左移当成带符号看(符号变了就出错)。右移的结果则在最高位是 0 时相同(算术右移补的符号位恰好是 0),只有最高位为 1 时才分道扬镳。
数据在不同宽度类型之间转换时的位级行为,见 C 语言中的类型转换。
四、补码算术右移是向下取整
这是本节最容易被想当然的一处。1001 算术右移一位得 1100
要用右移实现带符号数除以
以
这也解释了一个常见现象:编译器把 x / 2 编译成移位时,如果 x 是有符号类型,生成的从来不是一条 sar,而是先做符号判断加偏移、再移位的三四条指令;只有 unsigned 类型才会退化成一条 shr。
五、循环移位:不丢位的那一种
移出的位不丢弃,而是填回另一端。
| 操作 | 规则 |
|---|---|
| 不带进位循环左移 | 最高位移到最低位,同时送入 CF |
| 不带进位循环右移 | 最低位移到最高位,同时送入 CF |
| 带进位循环左移 | 最高位 → CF → 最低位(CF 参与循环) |
| 带进位循环右移 | 最低位 → CF → 最高位(CF 参与循环) |
8 位不带进位循环左移:10110011 → 01100111,最高位的 1 回到了最低位。带进位循环移位把 CF 串进移位链,可以把一个长数拆成若干机器字逐段移位——这是它在多字长数据上的典型用处。
🔴 循环移位不丢位,也不做乘除。 移出的位填回另一端,只改变位的排列、不改变"位的集合",所以它与原数没有任何简单的乘除关系。它的用途是三类:高低字节对调(大小端转换)、在多字节数据之间传递进位、以及位域的提取与拼接。
⚠️ 最后提一条贯穿全篇的前提:按什么宽度移,由类型宽度决定,不由数值决定。1011 在 4 位里既是 1101(0101(
考点速记
- 机器中小数点位置是约定的,因此左移
位就是 、右移 位就是 ;但左移可能溢出、右移一定丢精度。 - 补 0 还是补 1 取决于这串位被解释成无符号数还是带符号数——左移一律补 0(所以
sal与shl是同一条指令),右移补的是符号位的值,分歧只在右移;两种解释下的溢出判据也不同。 - 补码算术右移是向下取整,与 C 语言除法的向零截断在负数上必然差 1,用右移代替带符号除法必须先加偏移量;循环移位不丢位,用于字节对调与多字长间传递进位,不用于乘除。
这一节在真题里被考过的形式(下方「真题练习」里属于本篇的那几道):
- 给一个机器数,问分别做逻辑右移和算术右移得到什么:这是最直接的一道。逻辑右移高位补 0,算术右移高位补符号位——最高位是 1 时两者结果不同,是 0 时相同。
- 给一个机器数,问连做两次移位(如先算术右移 3 位再算术左移 2 位)的最终结果:逐步做,不要合并。算术右移补符号位、算术左移补 0,两次补的位不一样,合并成"净右移 1 位"会算错。
- 给两个机器数,问
这类表达式的结果: 是左移一位、 是右移一位,都按算术移位做(操作数是带符号整数),算完再判有没有溢出。⚠️ 中间某一步溢出时整个结果就是"溢出",不能接着往下算。
易错:算术右移补 0。它补的是符号位。
易错:把连续两次移位合并成一次净移位。两次补的位不同,必须逐步做。
易错:用算术右移直接代替带符号除法。它是向下取整,负数上与 C 的除法差 1。
易错:判算术左移溢出时拿错比较对象。要比的是"移出的那一位"与"移位后的新符号位"。
教材出处
- 移位与乘除
的等价关系、"没有乘(除)法运算线路时可用移位和加法实现乘(除)运算":唐朔飞《计算机组成原理》第 3 版 §6.2 定点运算,印刷页 p234 - 三种码制的算术移位补位规则表、"不论正数还是负数,移位后符号位均不变":同书印刷页 p234–p236
- 算术移位与逻辑移位的区别(含
01010011、10110010的对照示例):同书印刷页 p236 - 用右移实现带符号数除法须先加偏移量:袁春风《计算机组成与系统结构》第 3 版第 3 章,印刷页 p80
相关知识
乘除运算的基本原理与实现结构|C 语言中的类型转换|真值与机器数:四种编码的定义