引言:计算机如何理解正负?

你是否曾好奇,由无数0和1构成的计算机,是如何精确地表示和计算我们日常生活中的数字,尤其是像-20这样的负数?这个问题的答案,隐藏在计算机科学最基础、也最重要的一个知识点中——编码

为了让只能理解高低电平(即0和1)的电路能够处理带有“+”、“-”符号的整数,科学家们设计了一套精妙的编码体系。这其中,原码、反码和补码就是理解这套体系的三把关键钥匙。

本文将带你深入计算机的底层视角,不仅告诉你它们“是什么”,更会解释“为什么”需要它们,并手把手教你如何进行手动转换。读完本文,你将对计算机如何存储和运算数字有一个全新的、更为深刻的认识。


第一站:原码 (Sign-Magnitude) - 最直观的尝试

原码是最符合人类直觉的一种表示法。它将一个二进制数分为两部分:符号位数值位

  • 规则:在一个特定位数(例如8位)的二进制数中,最高位作为符号位(0代表正数,1代表负数),其余位则表示该数绝对值的二进制形式。

我们以8位系统为例:

  • +7的原码
    • 符号:正数,符号位为 0
    • 数值:7 的二进制是 111
    • 组合(补齐8位):00000111
  • -7的原码
    • 符号:负数,符号位为 1
    • 数值:7 的二进制是 111
    • 组合(补齐8位):10000111
1
2
+7的原码: 00000111
-7的原码: 10000111

原码的困境:

虽然直观,但原码有两个致命的缺陷,导致它未被现代计算机采纳用于运算:

  1. 存在两个“零”+0 的原码是 00000000,而 -0 的原码是 10000000。同一个数值“零”却有两种编码,这无疑会带来混乱。
  2. 减法运算极其复杂:计算机的核心是加法器。我们希望减法也能通过加法来完成,例如 7 - 7 应该等同于 7 + (-7)。但如果用原码直接相加:
    00000111 (+7) + 10000111 (-7) = 10001110 (结果是-14),这显然是错误的。计算机需要设计一套额外的、复杂的逻辑来判断符号位并执行“借位”等减法操作,大大增加了硬件设计的成本。

第二站:反码 (Ones’ Complement) - 解决减法问题的重要一步

为了解决原码的运算问题,反码应运而生。它像是通往最终解决方案(补码)的一座桥梁。

  • 规则
    • 正数:正数的反码与原码完全相同
    • 负数:负数的反码是在其原码的基础上,符号位保持不变,其余所有数值位按位取反(0变1,1变0)。

继续以8位系统为例:

  • +7的反码:与原码相同,为 00000111
  • -7的反码
    • 原码:10000111
    • 符号位1不变,数值位0000111取反得到1111000
    • 组合后:11111000
1
2
+7的反码: 00000111
-7的反码: 11111000

反码的进步与遗憾:

反码的出现,使得减法运算可以通过加法实现。但它依然没有解决“两个零”的问题:

  • +0 的反码:00000000
  • -0 的反码 (原码10000000取反):11111111

“双零”问题依然存在,这意味着硬件还需要做额外的判断。


第三站:补码 (Two’s Complement) - 现代计算机的最终选择

补码是现代计算机世界的基石。它完美地解决了原码和反码的所有问题。

  • 规则
    • 正数:正数的补码与原码完全相同
    • 负数:负数的补码等于其反码的末位加1

继续以8位系统为例:

  • +7的补码:与原码相同,为 00000111
  • -7的补码
    • 反码:11111000
    • 末位加1:11111000 + 1
    • 结果:11111001
1
2
+7的补码: 00000111
-7的补码: 11111001

补码的“天才”之处:

  1. 唯一的零

    • +0 的补码是 00000000
    • -0 的反码是 11111111,其补码为 11111111 + 1 = (1)00000000。在8位系统中,最高位的进位1会被自然溢出并丢弃,结果也是 00000000。从此,“零”的表示得到了统一。
  2. 加减法运算的统一
    现在,计算机可以放心地用加法器来做所有整数运算了。让我们再试一次 7 + (-7)

    1
    2
    3
    4
      00000111   (+7的补码)
    + 11111001 (-7的补码)
    ------------------
    (1)00000000 (结果)

    同样,溢出的进位1被舍去,结果是 00000000,即0。完美!这个简单的改变,让CPU的设计复杂度大大降低,运算效率极大提升。


实战演练:手算十进制数的“三码”

理论已经清晰,让我们以 +20-20 为例,完整地走一遍手动计算流程。

计算 +20 的三码(8位)

对于正数,一切都很简单:原码 = 反码 = 补码

  1. 十进制转二进制20 的二进制是 10100
  2. 确定符号位并补齐:正数,符号位为0。在8位系统中,前面用0补齐。
    00010100
  3. 结论
    • +20 原码:00010100
    • +20 反码:00010100
    • +20 补码:00010100

计算 -20 的三码(8位)

对于负数,我们需要严格遵循转换步骤:

  1. 求原码

    • 绝对值20的二进制是 0010100 (已补齐7个数值位)。
    • 因为是负数,符号位置为1
    • -20 原码: 10010100
  2. 求反码 (原码符号位不变,数值位取反):

    • 保持符号位 1 不变。
    • 数值位 0010100 按位取反得到 1101011
    • -20 反码: 11101011
  3. 求补码 (反码末位加1):

    • 11101011 + 1
    • -20 补码: 11101100

逆向工程:从补码还原十进制数

当你在调试时看到一个内存地址的值是 11101100,如何知道它代表的十进制数?

  1. 看符号位:最高位是1,说明这是一个负数。
  2. 补码求原码:对负数补码的逆向操作是 “末位减1,再取反(符号位除外)”
    • 11101100 - 1 = 11101011 (得到反码)
    • 对反码 11101011,符号位1不变,数值位1101011取反得到 0010100
    • 拼接得到原码: 10010100
  3. 读出数值
    • 符号位1代表负号 -
    • 数值位0010100转为十进制是 $1 \times 16 + 1 \times 4 = 20$。
    • 最终结果:-20

总结

类型 规则/特点 +20 (8位) -20 (8位)
原码 直观,符号位+绝对值 00010100 10010100
反码 负数原码数值位取反 00010100 11101011
补码 负数反码末位+1 00010100 11101100

理解原码、反码和补码,不仅仅是为了应付面试或考试。它是我们窥探计算机内部世界的窗口,是理解底层数据结构、C语言位运算、乃至CPU设计的基石。下一次当你编写代码时,请记住,你定义的每一个intlong,都在内存中以补码的形式,安静而高效地存在着。