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

2 的补码=各位取反再加 1;有了它,减法可以直接用加法电路算,负数也只是一个正常的二进制数。

📝 本节和前面几节的关系

2.3 讲移位时留下了伏笔:负数怎么表示。这一节给出计算机用的答案。

一、先看那个笨办法

最直觉的做法是【拿最高位当符号位】:0 表示正、1 表示负。 它有两个麻烦:一是出现了 +0 和 -0 两个零;二是加法器没法直接处理它,做加法要额外判断符号。 教材要的表示法必须满足一条:让减法也能用加法做——这样电路里只留加法器就够。

二、2 的补码怎么做

做法只有两步:各位取反,再加 1。 以 8 位求 -3 为例:3 是 0000 0011 → 取反 1111 1100 → 加 1 得 1111 1101。 此时 5 + (-3) 就是 0000 0101 + 1111 1101 = 1 0000 0010。 最高位进上来的 1 被丢掉,剩下 0000 0010,正是 2——减法就这么被做成了加法。

三、这招为什么管用

关键在一句话:一个数和它的补码相加,结果必然是一个【最高位进 1、低位全 0】的数。 而最高位那个 1 会溢出丢掉,于是结果是 0——这正是【加上相反数等于零】的定义。 所以补码不是巧合,是为加法器量身定做的负数表示法。 顺带一个结果:在这套表示法里【不够减】不存在,3 - 5 直接就得 1111 1110,它读作 -2。

四、代价:符号位借走了最高位

有得必有失:最高位被拿去当符号位,能表示的绝对值范围就小了一半。 8 位的范围因此从 0~255 变成 -128 ~ 127。 这个范围直接决定了后面所有【溢出】问题的边界——127 + 1 会变成 -128。 记住这条,以后见到【为什么加着加着变成负数了】,就知道答案在哪儿。

五、动手 Demo:用【2 的补码】把减法变成加法

补码的做法:各位取反,再加 1。 有了它,减法可以直接交给加法电路。

for a, b in [(5, 3), (3, 5), (10, 7)]:
    nb = (-b) & 0xFF
    print("%d - %d :  %s + %s = %s"
          % (a, b, format(a, "08b"), format(nb, "08b"), format(a + nb, "09b")))

实跑输出:

--------------------------------------------------------------------
用【2 的补码】把减法变成加法
--------------------------------------------------------------------

**补码的做法:各位取反,再加 1。** 有了它,减法可以直接交给加法电路。

  以 8 位为例,求 -3:
    3         0000 0011
    各位取反  1111 1100
    加 1      1111 1101   <- 这就是 -3

  于是 5 - 3 变成 5 + (-3):
      0000 0101   (5)
    + 1111 1101   (-3)
    -----------
    1 0000 0010   <- 最高位进上来的 1 丢掉,剩 0000 0010 = 2

  算式       被减数       减数的补码     用加法算出来的     正确结果
  -------------------------------------------------------------------
  5 - 3      00000101     11111101       100000010          2
  3 - 5      00000011     11111011       011111110          -2
  10 - 7     00001010     11111001       100000011          3

  注意 3 - 5 的结果:得 1111 1110,**它就是 -2 的补码**。
  所以这套表示法里没有【不够减】这回事——负数也是一个正常的二进制数。

  **一点代价:最高位被借去当符号位,8 位能表示的范围变成 -128 ~ 127。**

3 - 5 得到 1111 1110,它就是 -2 的补码——这个表示法里没有【不够减】这回事。 减法电路没了,只剩加法器——这是补码存在的全部理由。

30 秒实验

  1. 手算 -5 的 8 位补码(取反加 1),再用它算 8 + (-5)。
  2. 把 127 写成 8 位补码,加 1 看结果变成什么。

常见误区

  1. 以为补码只是【一种写法】。它是为了让加法电路能算减法而设计的,目的极明确。
  2. 忘记范围变了一半。8 位从 0~255 变成 -128~127,溢出就在这一步埋下。
自测三问(点开对答案) 1. 2 的补码怎么求? —— 各位取反,再加 1。 2. 补码为什么能让减法变成加法? —— 一个数与其补码相加会产生最高位进位,去掉这位后结果是 0,正是相反数的定义。 3. 8 位补码能表示的范围是多少? —— -128 ~ 127(最高位被用作符号位,范围少了一半)。

相关笔记