📄 一句话概括
左移一位就是乘 2,右移一位就是除 2——因为每一位的位权都翻了一倍。
📝 本节和前面几节的关系
2.2 讲清了位权,这一节看位权带来的第一个便宜:移位能代替乘除。
一、挪一位,位权全体翻倍
把 5 写成二进制:0101。 左移一位 → 1010,也就是 8 + 2 = 10,正好是 5 的两倍。 右移一位 → 0010,也就是 2,正好是 5 除以 2 取整。 原因就在 2.2 的位权上:整体挪一格,每一位从 2 的 k 次方变成了 2 的 k+1 次方。
二、硬件的账:移位几乎是免费的
真正做乘法要搭乘法器电路——面积大、级数多、慢。
而移位只是【把线接到隔壁的位上】,几乎不花时间、不占面积。
所以在硬件设计和程序优化里,乘 2 的幂常被写成移位:×8 写成左移 3 位。
这也解释了为什么初学汇编的人常看到 shl/shr 这种指令——它们不只是位运算,也是在算乘法。
三、移位不是万能的
移出去的位会丢:8 位的 1000 0000 左移一位,1 就出了边界,结果变成 0。 这就是溢出的最朴素形态——不是算错了,是位不够放。 另外右移对负数是另一回事:要不要保留符号,正是 2.5 要讲的算术右移与逻辑右移之别。
四、动手 Demo:左移一位就是乘 2
二进制的移位,正好对应十进制的乘 2 与除 2。 因为每往左挪一位,都等于把这个数每一位的位权翻了一倍。
for v in [3, 5, 6, 12, 25]:
print("%2d %s 左移 -> %s (%d) 右移 -> %s (%d)"
% (v, format(v, "08b"), format(v << 1, "08b"), v * 2,
format(v >> 1, "08b"), v // 2))
实跑输出:
--------------------------------------------------------------------
左移一位就是乘 2
--------------------------------------------------------------------
**二进制的移位,正好对应十进制的乘 2 与除 2。**
因为每往左挪一位,都等于把这个数每一位的位权翻了一倍。
原值 二进制 左移 1 位 等于 右移 1 位 等于
----------------------------------------------------------------
3 00000011 00000110 6 00000001 1
5 00000101 00001010 10 00000010 2
6 00000110 00001100 12 00000011 3
12 00001100 00011000 24 00000110 6
25 00011001 00110010 50 00001100 12
拿 5 看一遍就清楚:
5 = 0000 0101
左移一位 = 0000 1010 = 8 + 2 = 10 = 5 × 2
右移一位 = 0000 0010 = 2 = 5 ÷ 2(取整)
**乘除 2 在电路上不用真做乘法——把线挪一位就行,几乎不花时间。**
这也解释了为什么代码里【乘 2 的幂】常被写成移位。
5 写成 0101:左移成 1010(10),右移成 0010(2)——乘 2 与除 2 各就各位。 乘除 2 在电路上不用真做乘法——挪一位就行。
30 秒实验
- 用纸笔算:13 的 8 位二进制左移两位是多少?结果等于 13 乘几?
- 找一个 8 位二进制的数,一直左移,看它什么时候变成 0——那就是溢出。
常见误区
- 以为移位是【只有底层才用的技巧】。它是乘除 2 的幂的标准做法,高级语言编译器也在用。
- 以为右移总是等于除 2。对负数不一定——要看你用的是哪种右移。
自测三问(点开对答案)
1. 左移一位相当于什么运算? —— 乘以 2;右移一位则相当于除以 2 取整。 2. 为什么移位能代替乘除? —— 二进制每一位的位权都是 2 的幂,整体挪一格等于所有位权翻倍。 3. 移位会不会出错? —— 会——移出去的位会丢,这就是溢出;对负数还要区分算术右移与逻辑右移。- ☐ 下一节(2.4 便于计算机处理的【2的补码】)
相关笔记
- 二进制到底是什么 — 位权是这一节的根据
- 逻辑右移与算术右移的区别 — 右移在负数上的麻烦
- 🔵 计算机基础 · 索引 — 硬件底层的入门课
- ⚙️ 程序是怎样跑起来的 · 索引