⚙️ 第二章 用二进制来理解数据
📄 一句话概括

右移有两种:逻辑右移左边一律补 0,算术右移补的是符号位——正数一样,负数分家。

📝 本节和前面几节的关系

2.4 有了补码,负数正式进入舞台。负数一出现,2.3 讲过的右移就得分成两种。

一、两种右移,只差补什么

逻辑右移:不管这个数是什么,左边一律补 0。 算术右移:左边补的是符号位——正数补 0、负数补 1。 所以对正数,两种移法结果完全一样;只有在负数上,它们才分道扬镳。

二、看 -8 就明白了

-8 的 8 位表示是 1111 1000。 逻辑右移:变成 0111 1100,当成无符号数读是 124。 算术右移:变成 1111 1100,当成有符号数读还是 -4。 而我们想要的是 -8 ÷ 2 = -4——算术右移给对了,逻辑右移彻底给错。

三、为什么要保留两种

因为【移位】在程序里有两类完全不同的用途: 当算术用(有符号数除 2、除 2 的幂)→ 必须用算术右移,保留正负号的含义。 当数据搬家(位运算、哈希、图像处理、协议解析)→ 用逻辑右移,0 就是 0,不掺符号。 两种都有存在的必要——含糊地叫一句【右移】,在负数上就是一个 bug。

四、这条知识怎么用

看到代码里对可能为负的数做右移,先问一句:这里要的是【除 2】还是【搬家】。 要除 2 却写了逻辑右移,结果是错的;要搬家却写了算术右移,会莫名多出一堆 1。 这类 bug 特征很明显:只在负数上出错,正数全对。

五、动手 Demo:逻辑右移与算术右移差在哪

逻辑右移左边一律补 0;算术右移左边补的是符号位。 正数两种移法结果一样,负数才分道扬镳。

for v in (8, -8, -3):
    u = v & 0xFF
    lg, ar = u >> 1, v >> 1
    print("%3d  %s  逻辑 %s (%3d)   算术 %s (%3d)"
          % (v, format(u, "08b"), format(lg, "08b"), lg,
             format(ar & 0xFF, "08b"), ar))

实跑输出:

--------------------------------------------------------------------
逻辑右移与算术右移差在哪
--------------------------------------------------------------------

**逻辑右移:左边一律补 0,不管这个数是什么。**
**算术右移:左边补的是符号位——正数补 0,负数补 1。**
正数两种移法结果一样,**负数才分道扬镳**。

  值     8 位表示     逻辑右移 ->     当成无符号读     算术右移 ->     当成有符号读
  ----------------------------------------------------------------------------------
  8      00001000     00000100        4                00000100        4
  -8     11111000     01111100        124              11111100        -4
  -3     11111101     01111110        126              11111110        -2

  看 -8 那一行:
    逻辑右移 -> 0111 1100,当成无符号数是 124
    算术右移 -> 1111 1100,当成有符号数还是 -4
  **要保留正负号的含义,就必须用算术右移;把它当纯数据搬家,就用逻辑右移。**

  教材特意点出这一条,是因为它直接决定【负数除 2 对不对】:
    -8 ÷ 2 = -4,算术右移给的就是 -4;逻辑右移给的是 124——完全错了。

-8 逻辑右移得 0111 1100(当无符号读是 124),算术右移得 1111 1100(有符号读是 -4)。 要保留正负号的含义,就必须用算术右移。

30 秒实验

  1. 手算 -16 的算术右移一位,再用无符号读法算一遍逻辑右移,两个答案差多少?
  2. 在代码里对一个可能为负的变量做右移,看它给的是除法还是搬家。

常见误区

  1. 以为【右移就是除 2】。对负数不成立——要看你用的是哪种右移。
  2. 以为两种右移只是写法区别。在负数上它们是两种完全不同的运算。
自测三问(点开对答案) 1. 逻辑右移和算术右移的区别是什么? —— 逻辑右移左边一律补 0;算术右移补的是符号位。 2. 为什么负数上必须区分它们? —— 负数符号位为 1,逻辑右移会把负数变成一个大正数,除 2 的含义就丢了。 3. 什么时候该用逻辑右移? —— 把数据当纯粹的位来处理时(位运算、哈希、协议解析),此时 0 就是 0。

相关笔记