CC++ & Algorithm

Python补码与位运算

困难3
语言版本:C++Python
概述:用一个“秘密代号”和“开关操作”的故事,帮你搞懂补码和位运算,并学会用Python代码做数字小魔术。

玩转二进制:补码和位运算,让计算机听懂你的“暗号”

你有没有和朋友约定过“秘密代号”?比如,你们在传纸条时,把“3”写成“13”,只有你们俩知道其实13代表3。计算机也很喜欢这种把戏——它不会直接存储“减号”,而是用一种叫补码的“秘密代号”来表示负数。同时,计算机里所有的数字底层都是二进制位(0和1),就像一排整整齐齐的开关。位运算就是直接操作这些开关的神奇技巧,可以快速做判断、计算,甚至加密。

在Python中,你可以用补码和位运算做一些看起来很“魔法”的操作。下面我们就一起来揭秘吧!


1. 补码:你告诉我-3,计算机却偷偷记成...11101

为什么需要补码?

想象你有一块只有0~15的钟表,没有“负号”这个按钮。你想算 3 - 5,怎么办呢?钟表是循环的——从3往后拨5格,你会走到14。而14恰好就是 -2 在这个“钟表世界”里的代表!这个“代表”就是补码。

计算机也是一样:它只能用固定位数的二进制(比如8位、16位、32位)来存数字。为了表示负数,它把一半的数字(比如0127)当作正数,另一半(128255)当作负数的补码。这样,加减法就可以直接用加法电路完成,不用单独设计减法器。

补码怎么算?——“取反加一”

把正数变成负数的补码,口诀就四个字:取反加一
例如,想得到-3的二进制(假设只考虑4位):

  1. 先写出3的二进制:0011
  2. 取反(0变1,1变0):1100
  3. 加1:1101

这个 1101 就是-3的4位补码。如果你把它当作无符号数,它等于13。所以 (-3) & 0b1111 就会得到13,这和钟表例子里的14道理一样(因为0~15只有16个数,3-5 = -2,-2的补码在4位下是 1110,即14;-3是 1101,即13)。

Python的整数的位数是无限的,所以我们用 & 0b1111(只取最低4位)来看到补码效果:

# 用&取最后4位,看到-3的补码
print((-3) & 0b1111)   # 输出13,二进制1101
print((-2) & 0b1111)   # 输出14,二进制1110

生活例子:温度计上的“零下”

温度计有零上和零下,但度数显示只有数字。如果把零下5度记为“95”(假设量程是0~100),那么“95”就是-5的“补码”。这样,计算“当前温度 + 10”时,直接用加法即可,不用管正负。


2. 位运算:操纵二进制位的“开关”游戏

把二进制位想象成一排小开关:0表示“关”,1表示“开”。位运算就是对这些开关进行组合操作。Python提供了6种常用的位运算符。

&(与):两个都开才亮

5 & 3 → 101 & 011 = 001(即1)

生活例子:你和同桌都举手才能回答问题。x & 1 只检查最右边那一位——如果为1,说明x是奇数(就像开关亮了)。

num = 7  # 二进制111
if num & 1:
    print(f"{num} 是奇数")  # 输出:7是奇数

|(或):只要一个开就亮

5 | 3 → 101 | 011 = 111(即7)

生活例子:你或妈妈任意一个人同意,就能买零食。

^(异或):开关状态不同才亮

5 ^ 3 = 101 ^ 011 = 110(即6)

有趣的应用:异或可以用来“加密”。假设你想和好朋友传一个数字密码,两人约定一个“密钥”,把明文和密钥异或得到密文,对方再用同一个密钥异或就能还原。而且自己与自己异或会归零:a ^ a = 0

~(取反):把所有开关拨到对面

~5 → …11111010(即-6)

注意:~x 等于 -x-1。因为取反后符号位也变了,所以5的取反是-6。

<<(左移):开关向左移动,右边补0

5 << 1 → 1010(即10),相当于乘以2

生活例子:排队时,每个人向左挪一个位置,空出来的最后一个位置补一个新人(0)。左移1位就是乘以2,左移2位就是乘以4。

>>(右移):开关向右移动,丢掉移出的低位

5 >> 1 → 10(即2),相当于整除2(向下取整)

注意:Python中右移负数时是算术右移(高位补1,保持符号),而正数右移高位补0。

# 快速乘2和整除2
x = 5
print(x << 1)   # 输出10
print(x >> 1)   # 输出2

3. 新手最容易犯的3个错误

错误1:混淆取反 ~ 和负数

很多同学以为 ~5 等于 -5,实际等于 -6。记住公式:~x = -x - 1

错误2:对负数进行右移时,以为高位一律补0

Python的整数是无限位的,负数右移时,高位会补1(保持符号)。例如:

print((-5) >> 1)  # 输出-3(而不是-2)

因为-5的二进制(无限高位全是1)右移1位后,结果仍是负数。

错误3:用异或交换两个变量时,a和b不能是同一个变量

下面这段代码看似巧妙,但如果a和b指向同一个整数对象(比如 a = 5; b = 5),就会出错:

a = 5
b = 5
a = a ^ b  # a变成0
b = a ^ b  # 0 ^ 5 = 5
a = a ^ b  # 0 ^ 5 = 5
print(a, b)  # 输出5 5,看起来没毛病?

但要是写成 a ^= b; b ^= a; a ^= b,当a和b是同一个变量时(比如 a = b = 5),最终a和b都会变成0!因为第一步 a ^= a 使a=0,后续都是0。所以实际编程中,最好用临时变量,更清晰安全。


4. 完整示例:用位运算做一个“2的幂次方检测器”

下面是一个完整的程序,它读取用户输入的数字,然后用位运算判断它是不是2的幂次方(例如1,2,4,8,…),并输出补码的“秘密代号”。代码中有详细的注释。

# 判断一个数是否是2的幂次方(大于0且只有一位是1)
def is_power_of_two(n):
    # 2的幂次方特点是:n > 0 且 n & (n-1) == 0
    # 比如 8 (1000) & 7 (0111) = 0
    return n > 0 and (n & (n - 1) == 0)

# 主程序
while True:
    user_input = input("请输入一个整数(输入exit退出):")
    if user_input.lower() == "exit":
        break
    
    try:
        num = int(user_input)  # 把输入转成整数
    except ValueError:
        print("请正确输入整数!")
        continue
    
    # 判断奇偶(用&1)
    if num & 1:
        print(f"{num} 是奇数")
    else:
        print(f"{num} 是偶数")
    
    # 判断是否是2的幂次方
    if is_power_of_two(num):
        print(f"? {num} 是2的幂次方!")
    else:
        print(f"{num} 不是2的幂次方")
    
    # 展示补码“秘密代号”(取低4位)
    if num < 0:
        secret_code = num & 0b1111
        print(f"{num} 的4位补码秘密代号是 {secret_code}(二进制{bin(secret_code)})")
    else:
        print(f"{num} 的二进制是 {bin(num)}")
    
    print("-" * 30)

运行示例:

请输入一个整数(输入exit退出):8
8 是偶数
? 8 是2的幂次方!
8 的二进制是 0b1000
------------------------------
请输入一个整数(输入exit退出):-3
-3 是奇数
-3 不是2的幂次方
-3 的4位补码秘密代号是 13(二进制0b1101)
------------------------------

5. 相关指引:接下来学什么?

补码和位运算是计算机底层的重要概念。掌握了它们,你可以:

  • 学习二进制、八进制、十六进制的转换,更深入理解数据如何在计算机中存储。
  • 了解原码、反码与补码的关系,知道为什么计算机采用补码。
  • 用位运算优化程序性能(比如判断2的幂、快速求平均值等)。
  • 探索图像编码、网络协议中如何用位来标记状态。
  • 学习汇编语言时,位运算和补码更是基本功。

如果你觉得有趣,还可以试试写一个“闪烁LED灯”的程序(用位运算控制RGB颜色),或者做一个简单的“加密解密”工具(用异或)。记住,计算机里的秘密,都藏在0和1的开关之间!

例题精讲

1单选题

在Python中,表达式 ~5 的结果是什么?

A-6
B6
C-5
D4
2判断题

在Python中,表达式 15 & 7 的结果是 7。

3填空题
以下代码用于判断整数n是否为偶数,请填写恰当的位运算表达式:
if n ___ 1 == 0:
    print("偶数")
4单选题

在Python中,表达式 (-8) & 0xFF 的结果是多少?

A248
B-8
C0
D255
5填空题
以下代码使用异或运算交换两个整数 a 和 b 的值,请补充最后一行的表达式:
a = a ^ b
b = a ^ b
a = ___