现在位置: 首页 > 计算机组成原理 > 正文

负数怎么表示——原码、反码、补码

计算机只认识 0 和 1,那么负数是怎么表示的?

例如,十进制的 5 可以写成二进制 00000101,但 -5 应该怎么表示?

最直接的想法是拿出一位来表示正负号,但这样又会引出新的问题:负数该怎么参与加法?为什么 8 位整数的范围是 -128 到 127,而不是 -127 到 128?

要理解这些问题,就需要了解计算机表示有符号整数的三种方式:原码、反码和补码。

下面以 8 位二进制为例,通过图形和交互演示,逐步理解它们的区别。

一、8 位二进制如何表示正负数?

8 位二进制共有 2^8 = 256 种不同的位模式。

如果按照无符号整数解释,可以表示 0 到 255;如果按照补码解释,可以表示 -128 到 127。

8 位二进制结构示意图(以原码为例)
+5 原码 0 0000101
-5 原码 1 0000101
符号位 0:正数 符号位 1:负数 其余 7 位:绝对值

注意:这里展示的是原码。补码虽然也可以通过最高位识别正负,但最高位在数值计算中具有负权重,不能简单理解为独立符号位。

二、原码:最直观的表示方式

原码(Sign-Magnitude)采用最高位表示符号,其余位表示绝对值。

它的问题在于有两个零:

原码中的正零和负零
+0 0 0000000
-0 1 0000000

两个不同的二进制位模式表示同一个数值 0,增加了数值比较和运算的复杂度。

另外,原码不能简单地将全部二进制位直接相加来完成有符号加法。

例如,按 8 位原码直接计算 5 + (-3),会得到 10001000,按原码解释是 -8,而正确答案应该是 2。

因此,原码运算需要额外处理操作数符号及绝对值的加减。

三、反码:把二进制位取反

反码(One's Complement)的规则是:

  • 正数的反码与原码相同。
  • 负数的反码由对应正数的二进制位逐位取反得到。

以 -5 为例:

-5 的反码计算过程
+5 二进制 0 0000101

每一位都取反:0 变成 1,1 变成 0。

-5 的反码 1 1111010
00000101 → 11111010

反码依然存在两种零表示:

+0 = 00000000
-0 = 11111111

因此,反码同样无法充分利用所有位模式来表示不同的整数。

四、补码:取反再加 1

补码(Two's Complement)解决了原码和反码中正零、负零重复表示的问题。

对于负数,一个常用的补码计算方法是:

先写出绝对值的固定位宽二进制,再逐位取反,最后加 1。

图解:-5 的补码计算

从 +5 到 -5 的 8 位补码
① +5 00000101
② 逐位取反 11111010
③ 加 1 11111011
00000101 (+5)
11111010 (取反)
11111011 (加 1,得到 -5 的补码)

因此,-5 的 8 位补码为 11111011。

三种表示方式对比

-5 的原码、反码、补码
原码 10000101

符号位 + 绝对值

反码 11111010

正数位模式取反

补码 11111011

取反再加 1

五、为什么补码可以直接做加减法?

补码的核心原理是模运算。

8 位二进制有 256 种位模式,固定宽度加法相当于对 256 取模。

例如,计算 5 - 3:

5 + (-3) 的补码加法
+5 00000101
+
-3 的补码 11111101
→
结果 00000010
  00000101
+ 11111101
------------
1 00000010
  ↑
丢弃超出 8 位的进位
结果:00000010 = 2

也可以使用十进制模运算理解:

5 + (256 - 3) = 258
258 mod 256 = 2

这就是补码能够用统一的固定位宽加法机制处理减法的原因。

六、4 位补码环形演示

如果只有 4 位二进制,就有 16 种位模式。

按照补码解释,这 16 种位模式分别对应 -8 到 7。

下面的交互图可以观察数值变化时,补码如何在固定位宽的位模式中循环。

交互演示:4 位补码
-3

当前补码:1101

对应无符号值:13

从环形图可以发现:

  • 0 到 7 使用 0000 到 0111。
  • -8 到 -1 使用 1000 到 1111。
  • 在 4 位模 16 加法中,7 加 1 得到位模式 1000,解释为 -8。
  • -1 的位模式 1111 加 1 后,保留低 4 位得到 0。

七、交互式补码计算器

输入 -128 到 127 范围内的整数,即可查看对应的 8 位原码、反码和补码。

8 位整数编码计算器
原码
反码
补码

特别注意:8 位原码和反码的表示范围为 -127 到 127,不能表示 -128。而 8 位补码可以表示 -128,对应位模式为 10000000。

八、补码的最高位为什么表示负数?

在 8 位补码中,可以将每一位理解为具有不同的数值权重。

与无符号整数不同,补码最高位的权重是 -128,而不是 +128。

8 位补码的位权重
位位置 76543210
位权重 -128 6432168421
-1 的位 11111111
11111111
= -128 + 64 + 32 + 16 + 8 + 4 + 2 + 1
= -1

因此,补码中的最高位并不只是一个独立的正负标志,它也参与整个数值的计算。

九、原码、反码、补码完整对比

数值原码反码补码
+5000001010000010100000101
-5100001011111101011111011
+0000000000000000000000000
-01000000011111111不存在独立表示
+127011111110111111101111111
-127111111111000000010000001
-128无法表示无法表示10000000

十、补码溢出图解

8 位补码的表示范围为 -128 到 127。如果真实数学结果超出这个范围,就会发生有符号溢出。

8 位模运算:127 + 1
127 01111111
+1 00000001
低 8 位结果 10000000

数学结果为 128,超出 8 位补码的最大值 127。保留低 8 位后得到 10000000,按照 8 位补码解释为 -128。

需要注意,这只是固定 8 位、按模 256 截断的运算结果。

  • Python 的普通整数不会因为超过 8 位就自动环绕。
  • Java 的有符号整数存在固定宽度的溢出行为,但 byte 参与算术运算时通常先提升为 int。
  • C/C++ 中的有符号整数算术溢出通常属于未定义行为,不能直接依赖这种回绕结果。

十一、Python 实现补码转换

Python 可以通过位运算模拟指定位宽的补码。

实例

def to_twos_complement(n, bits=8):
    """将整数转换为指定位宽的补码字符串。"""
    if bits < 1:
        raise ValueError("位宽必须大于等于 1")

    minimum = -(1 << (bits - 1))
    maximum = (1 << (bits - 1)) - 1

    if not minimum <= n <= maximum:
        raise ValueError("整数超出补码表示范围")

    mask = (1 << bits) - 1
    return format(n & mask, f"0{bits}b")


def from_twos_complement(binary):
    """将补码字符串转换为十进制整数。"""
    if not binary or any(c not in "01" for c in binary):
        raise ValueError("请输入有效的二进制字符串")

    unsigned = int(binary, 2)
    bits = len(binary)

    if binary[0] == "1":
        return unsigned - (1 << bits)

    return unsigned


for n in [5, -5, -20, 0, 127, -128]:
    binary = to_twos_complement(n)
    restored = from_twos_complement(binary)

    print(f"{n:4d} -> {binary} -> {restored}")

执行结果:

   5 -> 00000101 -> 5
  -5 -> 11111011 -> -5
 -20 -> 11101100 -> -20
   0 -> 00000000 -> 0
 127 -> 01111111 -> 127
-128 -> 10000000 -> -128

十二、常见问题

为什么补码只有一种零表示?

因为在固定 n 位补码中,0 的位模式只有全零这一种。对零取反加 1,按固定宽度截断后仍然得到全零。

为什么 -128 取反加 1 还是 -128?

在 8 位补码中,-128 的位模式为 10000000。逐位取反加 1 后,保留低 8 位仍是 10000000。

原因是 +128 超出 8 位补码的表示范围,因此最小负数没有可表示的正数相反数。

补码和二进制是同一个概念吗?

不是。二进制是使用 0 和 1 表示数据的计数和编码基础;补码则是一种解释固定位宽二进制位模式的有符号整数编码方式。

总结

原码、反码和补码都是有符号整数的编码方式。

原码比较直观,但存在正零、负零和符号运算复杂的问题;反码通过逐位取反表示负数,但仍然存在两个零;补码则只保留一种零表示,并允许利用模运算统一处理固定宽度的加减法。

理解补码时,最值得记住的是:

  • 负数的补码可以通过对应正数的二进制取反加 1 得到。
  • 8 位补码的范围是 -128 到 127。
  • 补码最高位的位权重为负数。
  • 固定 n 位加法可以理解为模 2^n 运算。
  • 不同编程语言对有符号整数溢出的处理规则不一定相同。