Python补码与位运算
困难3玩转二进制:补码和位运算,让计算机听懂你的“暗号”
你有没有和朋友约定过“秘密代号”?比如,你们在传纸条时,把“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位):
- 先写出3的二进制:
0011 - 取反(0变1,1变0):
1100 - 加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的开关之间!
例题精讲
在Python中,表达式 ~5 的结果是什么?
在Python中,表达式 15 & 7 的结果是 7。
以下代码用于判断整数n是否为偶数,请填写恰当的位运算表达式:
if n ___ 1 == 0:
print("偶数")在Python中,表达式 (-8) & 0xFF 的结果是多少?
以下代码使用异或运算交换两个整数 a 和 b 的值,请补充最后一行的表达式:
a = a ^ b
b = a ^ b
a = ___