CC++ & Algorithm

为什么 `~5` 是 -6?——从电灯开关到补码的奇妙旅程

2026年9月1日关联知识点:二进制与计算机2 次阅读

你有没有想过,一个简单的“取反”操作,为什么在 Python 里会给你一个看起来毫不相干的负数?

~5 的结果是 -6,而不是你直觉中的“某个很大的数”。如果你第一次见到这个结果时一脸懵,恭喜你,你正在触碰计算机最底层、最优雅的秘密之一。

今天,我想带你从电灯开关出发,一路走到补码和位运算。你会发现,这些看似抽象的概念,其实就藏在我们每天用到的每一个程序里。

一切从开关开始

想象你面前有一排电灯开关。每个开关只有两种状态:开或关。如果我用“开”表示 1,“关”表示 0,那么这一排开关就能表示一个二进制数。

这就是计算机的底层逻辑:数以亿计的微型开关(晶体管)通过“有电/没电”两种状态存储和运算数据。为什么用二进制?因为电路实现最简单、最可靠——只需要判断高低电平,不需要区分 10 种不同的电压等级。

我见过一道非常经典的题目,问“计算机内部为什么要采用二进制”。很多人会选“二进制运算规则最简单”,但正确答案是 “二进制容易用电路的高低电平实现” 。运算规则简单只是副产品,核心原因是物理实现的便利性。记住这一点,你就抓住了计算机设计的根本逻辑。

二进制:计算机的母语

在 Python 中,用 0b 前缀表示二进制数:

a = 0b101   # 十进制的 5
b = 0b110   # 十进制的 6
print(a + b)  # 输出 11,二进制是 0b1011

转换也很简单:bin(10) 得到字符串 '0b1010'int('0b1010', 2) 转回十进制。

但这里有个新手常踩的坑:bin() 返回的是字符串,不能直接做数学运算。你得先 int(..., 2) 转回整数。

另一个经典题目是“二进制数 (1011)₂ 转十进制”。答案是 11,计算过程是 1×2³ + 0×2² + 1×2¹ + 1×2⁰ = 8+0+2+1 = 11。这题看似简单,但考试时总有人算错——要么忘了最高位的权值是 8,要么把中间那个 0 的位权搞混。我的建议是:从右往左,逐位标记权值,写出完整算式,别心算。

负数怎么表示?——补码的魔法

正数好办,负数呢?计算机里没有负号,只有 0 和 1。于是天才的工程师们发明了补码

先看原码和反码,它们是理解补码的阶梯:

  • 原码:最左边一位是符号位,0 正 1 负。+5 是 00000101,-5 是 10000101。直观,但做减法会出大问题。
  • 反码:正数不变,负数符号位不变、其他位取反。-5 的反码是 11111010。解决了部分问题,但 0 有两种表示,加法还是别扭。
  • 补码:正数不变,负数是反码加 1。-5 的补码是 11111011

为什么补码是最终赢家?因为它让减法变成加法。计算 5 + (-5),用补码就是 00000101 + 11111011 = 1 00000000,最高位溢出丢弃,剩下 00000000,正好是 0。

这就像时钟:现在是 3 点,想知道 2 小时前是几点,可以逆时针拨 2 格(3-2=1),也可以顺时针拨 10 格(3+10=13,mod 12 = 1)。-2 的补码等价于 +10,因为 10 是 12 减去 2。在 8 位二进制里,-5 的补码 11111011 当无符号数看是 251,而 251 + 5 = 256 = 2⁸,溢出归零,完美。

有个生活化的比喻:你的钱包只能装 0~255 元。你有 5 元,妈妈要扣掉 5 元。她不是直接拿走 5 元,而是给你加了一笔“反向”的 251 元——你的钱变成 256 元,钱包溢出归零,最后空了。绕是绕了点,但计算机用这套逻辑做减法,电路简单又可靠。

在 Python 里模拟 8 位补码:

def to_complement8(n):
    if n >= 0:
        return format(n & 0xFF, '08b')
    else:
        return format(((-n) ^ 0xFF) + 1 & 0xFF, '08b')

print(to_complement8(5))   # 00000101
print(to_complement8(-5))  # 11111011

注意 & 0xFF 是为了截断到 8 位。Python 的整数无限精度,负数补码前面有无数个 1,不截断会得到一长串 1

位运算:为什么 ~5 是 -6?

有了补码,位运算的“怪事”就讲得通了。

Python 的整数是无限精度的,所以负数以无限长的补码存在(前面无数个 1)。~5 的过程是:

  1. 5 的补码(无限位):...00000101
  2. 按位取反:...11111010
  3. 这是一个负数的补码。转回十进制:先减 1 得 ...11111001,再取反得 ...00000110,即 6,加上负号就是 -6

所以 ~5 = -6~(-5) = 4

我常跟学生说:把数字想象成无限长的黑白棋盘,白色是 0,黑色是 1。5 的棋盘是“全白、白、黑、白、黑”(从右往左:101)。全部翻转颜色,得到“全黑、黑、白、黑、白”,这个新棋盘代表的数就是 -6。这个比喻虽然有点抽象,但一旦建立起画面感,位运算就不再神秘。

其他位运算也基于补码:

a = 5   # 101
b = 3   # 011

print(a & b)   # 1
print(a | b)   # 7
print(a ^ b)   # 6
print(~a)      # -6
print(a << 1)  # 10(左移1位相当于乘2)
print(a >> 1)  # 2(右移1位相当于整除2)

特别注意:负数右移时,高位补 1(算术右移),所以 -5 >> 1 结果是 -3,而不是 -2。这是新手最容易踩的坑。

一个完整的例子

把上面所有知识串起来:

num = int(input("请输入一个整数:"))

print(f"bin({num}) = {bin(num)}")
print(f"8位补码:{to_complement8(num)}")
print(f"{num} & 0b1111 = {num & 0b1111}")
print(f"{num} ^ 10 = {num ^ 10}")
print(f"~{num} = {~num}")
print(f"{num} << 2 = {num << 2}")
print(f"{num} >> 2 = {num >> 2}")

输入 -5,你会看到 -5 ^ 10 = -15,因为 ...11111011 ^ ...00001010 = ...11110001,这是 -15 的补码。这个例子很有价值:位运算的结果取决于操作数的无限精度补码,不是简单的 8 位或 32 位。

进阶建议

掌握了二进制、补码和位运算,你就摸到了计算机底层的大门。接下来可以探索:

  • 浮点数编码:为什么 0.1 + 0.2 != 0.3?答案藏在 IEEE 754 标准里。
  • 字符编码:ASCII 和 Unicode 如何用二进制表示文字。
  • 位运算实战:权限控制(Unix 文件权限)、状态标志、图像处理中的像素操作。
  • CPU 加法器:补码如何让硬件实现减法。

每当你看到 ~5 = -6 这种“怪事”,别慌,在脑海中画出那个无限长的黑白棋盘,一切都会变得清晰。二进制是计算机的母语,学会它,你和机器的对话就更深了一层。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

评论0

还没有评论,来抢沙发~

评论加载中...

想系统学习这个知识点?查看完整知识点 →