CC++ & Algorithm

Python中原码、反码和补码

困难4
语言版本:C++Python
概述:用温度计和钟表比喻,带Python代码,帮小学生轻松理解计算机如何用原码、反码、补码表示正负数。

计算机里的正负数:原码、反码和补码

你有没有想过,计算机里面只认识0和1,它是怎么表示“负数”的呢?比如 -5 这个数,如果直接写成 101(二进制),那它到底是“负三”还是“五”?聪明的计算机科学家发明了三套“密码”——原码、反码和补码,专门用来解决这个问题。

其实这三套码就像我们生活中的两种常见工具:温度计钟表。温度计告诉我们0度以上和以下怎么区分;钟表告诉我们怎么用加法来算减法。今天我们就用这两个比喻,加上Python代码,一起来认识它们。


为什么需要三种码?

计算机原本想得很简单:用最左边一个比特(bit)表示正负。0表示正,1表示负,剩下的比特表示数值大小。这就是原码。比如 +5 写成 00000101,-5 写成 10000101。看起来挺直接,对吧?

但是计算机做加减法的时候出问题了。如果想让计算机算 3 + (-5) ,你得先判断符号,再决定是做加法还是减法,还得考虑借位,非常麻烦。能不能让加减法都用同一个加法电路完成呢?

大家想到了钟表的妙处:钟表盘上只有1到12,如果现在是3点,想回拨5小时(也就是减5小时),我们可以直接加上7(因为 3-5 相当于 3+7,12点算作0,结果就是10点)。在数学里,这就是 模12 的运算。补码就是模仿钟表:把一个负数表示成它的“补数”,这样减法就变成了加法。

为此,人们先发明了反码作为过渡,最后得到了补码。计算机实际存储的都是补码。


原码:最直接的表示

规则

  • 最高位(最左边)是符号位,0表示正数,1表示负数。
  • 剩下的位表示数值的绝对值(用二进制)。

生活例子:就像成绩单上的“+15分”和“-3分”,数字前面的正负号就是符号位,分数就是数值。

Python模拟(以8位为例):
对于正数,直接在前面补0到8位。
对于负数,先求出绝对值的二进制(用7位),然后在最前面加一个1。

def yuan_ma(num, bits=8):
    """返回原码的二进制字符串,长度为bits"""
    if num >= 0:                               # 正数或0
        return format(num, '0{}b'.format(bits))
    else:                                      # 负数
        abs_bin = format(abs(num), '0{}b'.format(bits-1))  # 绝对值用后7位
        return '1' + abs_bin                   # 前面加符号位1

print("+5的原码:", yuan_ma(5))
print("-5的原码:", yuan_ma(-5))
print(" 0的原码:", yuan_ma(0))

运行结果:

+5的原码: 00000101
-5的原码: 10000101
 0的原码: 00000000

注意:0有两种原码表示:+0是00000000,-0是10000000。但计算机不喜欢有两个0,所以后面有了补码。


反码:为补码做准备

规则

  • 正数和0的反码 = 原码(不变)。
  • 负数的反码 = 符号位保持1,其余各位(数值位)全部取反(0变1,1变0)。

生活例子:就像考试答案的“反着看”——把选择题选项全部反过来,但最前面的题号不变。

Python模拟:我们可以根据原码来生成反码。

def fan_ma(num, bits=8):
    """返回反码的二进制字符串"""
    original = yuan_ma(num, bits)             # 先得到原码
    if num >= 0:                               # 正数直接返回原码
        return original
    else:                                      # 负数:符号位不变,后面取反
        inv_body = ''.join('1' if c == '0' else '0' for c in original[1:])  # 数值位取反
        return '1' + inv_body

print("+5的反码:", fan_ma(5))
print("-5的反码:", fan_ma(-5))
print(" 0的反码:", fan_ma(0))

运行结果:

+5的反码: 00000101
-5的反码: 11111010
 0的反码: 00000000

这里 +0 和 -0 的反码也不同:+0是00000000,-0是11111111。还是有两个0。


补码:计算机真正用的编码

规则

  • 正数和0的补码 = 原码(不变)。
  • 负数补码 = 反码 + 1(并且忽略最高位的进位)。

生活例子:就像钟表上的“-3点”可以用“9点”表示(12-3=9)。计算机用补码,把负数变成它的“补数”,这样加减法就统一了。比如 5 + (-5) 用补码算就是 00000101 + 11111011 = 1 00000000,忽略最高位的进位1,得到00000000,正好是0。

Python模拟:可以用一种很巧妙的方法——直接用Python整数做位与运算&)。因为Python的整数无限大,但我们可以让它只保留最后bits位:num & ((1 << bits) - 1)

def bu_ma(num, bits=8):
    """返回补码的二进制字符串"""
    if num >= 0:                               # 正数直接返回原码
        return format(num, '0{}b'.format(bits))
    else:
        # 用位运算:num & (2^bits - 1) 得到补码对应的整数
        comp_val = num & ((1 << bits) - 1)     # 例如 -5 & 255 得到 251
        return format(comp_val, '0{}b'.format(bits))

print("+5的补码:", bu_ma(5))
print("-5的补码:", bu_ma(-5))
print(" 0的补码:", bu_ma(0))

运行结果:

+5的补码: 00000101
-5的补码: 11111011
 0的补码: 00000000

注意:+0和-0的补码都是00000000!这就是补码的好处:只有一个0,没有正负0的麻烦。

小窍门:负数补码也可以用“原码数值位取反再加1”来手工计算。比如 -5 原码 10000101,数值位 0000101 取反得 1111010,再加1得 1111011,整体符号位不变,就是 11111011。和上面结果一致。


总结与常见错误

总结

数字原码反码补码
+5000001010000010100000101
-5100001011111101011111011
000000000 或 1000000000000000 或 1111111100000000
  • 正数:三种码一模一样。
  • 负数:补码 = 原码数值位取反 + 1(注意符号位不变)。
  • 计算机只存补码,所以加减法都可以用一套加法电路,又快又省电。

常见错误

  1. 把原码当作计算机实际存储:错了!计算机内部统一用补码,原码只是方便人理解。
  2. 误认为负数补码就是反码加1,但忘了符号位:符号位始终是1,取反加1只针对数值位。比如 -5 的补码是 11111011,符号位1没变。
  3. 计算补码时直接对原码整体加1:比如拿着 10000101 直接加1得到 10000110,这是错的。应该先对数值位(后7位)取反,再加1。
  4. 以为0的补码有两种:不,补码下0只有一种表示:全0。
  5. 忽略位数:8位和16位的结果不同。比如 -5 在8位下是11111011,在16位下是11111111 11111011。

完整代码演示

下面把上面三个函数整合起来,一次性展示多个数字的三种码:

def show_codes(num, bits=8):
    """显示数字num的原码、反码、补码(bits位)"""
    # 原码
    if num >= 0:
        original = format(num, '0{}b'.format(bits))
    else:
        abs_bin = format(abs(num), '0{}b'.format(bits-1))
        original = '1' + abs_bin

    # 反码
    if num >= 0:
        inverse = original
    else:
        inv_body = ''.join('1' if c == '0' else '0' for c in original[1:])
        inverse = '1' + inv_body

    # 补码
    if num >= 0:
        complement = original
    else:
        comp_val = num & ((1 << bits) - 1)
        complement = format(comp_val, '0{}b'.format(bits))

    print(f"数字 {num:>3}:原码={original}  反码={inverse}  补码={complement}")

# 测试几个数字
show_codes(5)
show_codes(-5)
show_codes(0)
show_codes(-1)
show_codes(127)   # 最大正数
show_codes(-128)  # 最小负数(8位范围-128~127)

运行结果:

数字   5:原码=00000101  反码=00000101  补码=00000101
数字  -5:原码=10000101  反码=11111010  补码=11111011
数字   0:原码=00000000  反码=00000000  补码=00000000
数字  -1:原码=10000001  反码=11111110  补码=11111111
数字 127:原码=01111111  反码=01111111  补码=01111111
数字 -128:原码=10000000  反码=11111111  补码=10000000

发现了吗?-128 的补码是 10000000,因为它的原码数值位是 0000000,取反得 1111111,加1得 10000000,进位被截断,结果符号位又变回1了。这正好是8位补码能表示的最小负数。


相关知识点指引

如果你还想了解更多,可以继续学习:

  • 二进制基础:了解2的幂、位权、十进制与二进制互转(例如:101表示1×4 + 0×2 + 1×1 = 5)。
  • 位运算:Python中的 &(与)、|(或)、~(取反)、<<(左移)、>>(右移)。补码的计算离不开这些运算。
  • Python整数无限大:Python的整数其实可以无限大,但当我们用 & 截断成固定位数时,就模拟了定长整数的补码。
  • 负数在计算机中的实际存储:比如用 bin(-5) 会输出 -0b101,这不是补码,但你可以用 -5 & 0xFF 得到补码对应的整数251。
  • 溢出问题:如果两个正数相加结果超过了127(8位),补码会变成负数,这就是溢出,需要程序员小心判断。

下次你看到二进制 11111011,别再以为是“大数”了——它可能代表 -5 哦!

例题精讲

1单选题

在8位二进制系统中,十进制数+5的原码、反码、补码分别是多少?

A原码00000101,反码11111010,补码11111011
B原码10000101,反码11111010,补码11111011
C原码00000101,反码00000101,补码00000101
D原码00000101,反码11111010,补码00000101
2判断题

在计算机中,使用补码表示负数可以简化加法运算,因为减法可以转化为加法。

3填空题
已知 x = -6,假设使用8位二进制,则 x 的补码表示为 ___
4单选题

下列关于原码的描述,哪一个是错误的?

A原码的符号位用0表示正,用1表示负
B原码中正数的数值位与真值的二进制表示相同
C原码中0有两种表示形式:00000000和10000000
D原码可以直接进行加法运算而不需要任何额外处理
5判断题

负数的反码与它的补码相差1(不考虑进位溢出)。