1. 原码、反码和补码
计算机中的有符号数有三种表示方法,即原码、反码和补码,三种表示方法均有符号位和数值位两部分,在计算机系统中,数值一律用补码来表示和存储。
符号位:最高位为符号位,0表示正、1表示负。在位运算中,符号位也参与运算。
原码:即数字正常的二进制表示,其中有一位符号位:
# 3 和 -3 的原码:
# 00 00 00 11 : 3
# 10 00 00 11 : -3
反码:正数的反码就是原码,负数的反码是其原码的符号位不变,其他位取反:
# 3 和 -3 的反码:
# 00 00 00 11 : 3
# 11 11 11 00 : -3
补码:正数的补码就是原码,负数的补码是其反码+1:
# 3 和 -3 的补码:
# 00 00 00 11 : 3
# 11 11 11 01 : -3
在计算机中使用补码:
- 可以将符号位和数值域统一处理
- 加法和减法也可以统一处理
- 补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
2. 按位运算
按位非:~
把 n
的补码中的0和1全部取反(0变1,1变0):
# 5 的补码 : 00 00 01 01
# ~5 : 11 11 10 10 : -6
# -5的补码 : 11 11 10 11
# ~-5 : 00 00 01 00 : 4
可以简单总结出来一个计算结论: ~n = -(n+1)
按位与:&
当 n
和 m
对应位都是1的时候才输出1,其他情况全是0:
# 5 的补码 : 00 00 01 01
# 6 的补码 : 00 00 01 10
# 5 & 6 : 00 00 01 00 : 4
按位或:|
当 n
和 m
对应位只要有一个1就输出1,其他情况输出0:
# 5 的补码 : 00 00 01 01
# 6 的补码 : 00 00 01 10
# 5 | 6 : 00 00 01 11 : 7
按位异或:^
只要 n
和 m
对应位不一样就输出1(异或操作满足交换律和结合律):
# 5 的补码 : 00 00 01 01
# 6 的补码 : 00 00 01 10
# 5 ^ 6 : 00 00 00 11 : 3
# 6 ^ 5 : 00 00 00 11 : 交换律
# 5 ^ 6 ^ 5 : 00 00 01 10 : 6
# 5 ^ 5 ^ 6 : 00 00 01 10 : 结合律
# 本例比较特殊:5与自己异或结果为0,而0与任何数异或结果为任何数
按位左移:<<
n << i
表示将n的二进制数向左移动 i 位,空位补0:
# 5 的补码 : 00 00 01 01
# 5 << 2 : 00 01 01 00 : 20
按位右移:>>
n >> i
表示将n的二进制数向右移动 i 位:
# 5 的补码 : 00 00 01 01
# 5 >> 2 : 00 00 00 01 : 1
3. 借助位运算做一些骚操作
通过 <<
和 >>
快速计算2的倍数
n = 16, m = 3
# n << m # 128 解读:计算 n*(2^m),即 n 乘以 2 的 m 次方
n >> m # 2 解读:计算 n/(2^m),即 n 除以 2 的 m 次方
1 << m # 8 解读:计算 1*2^m,即 2 的 m 次方
通过 ^
快速交换两个整数
a, b = 3, 4
a ^= b # 此时 a = 7, b = 4
b ^= a # 此时 a = 7, b = 3
a ^= b # 此时 a = 4, b = 3
利用位运算实现一个整数集合
一个数的二进制表示可以看成是一个集合(0-不在集合中,1-在集合中),那么集合 {1, 3, 4, 8} 可表示为:
# 01 00 01 10 10 二进制集合表示
# 98 76 54 32 10 对应位置代表的集合元素,(1,3,4,8处标记为1表示在集合中)
根据这样的思路,可以得出元素与集合的操作:
a | (1<<i) # 把 i 插入到集合中
a & ~(1<<i) # 把 i 从集合中删除
a & (1<<i) # 判断 i 是否属于该集合(零不属于,非零属于)
集合之间的操作:
~a # a 的补
a & b # a 交 b
a | b # a 并 b
a & (~b) # a 差 b
注意:整数在内存中是以补码的形式存在的,输出自然也是按照补码输出。需要额外注意的是,在Python中:
print(bin(3)) # 0b11
print(bin(-3)) # -0b11 为3的原码直接加了个负号
print(bin(-3 & 0xffffffff)) # 0b11111111111111111111111111111101
print(bin(0xfffffffd)) # 0b11111111111111111111111111111101
print(0xfffffffd) # 4294967293
是不是很颠覆认知,我们从结果可以看出:
- Python中
bin
一个负数(十进制表示),输出的是它的原码的二进制表示加上个负号 - Python中的整型是补码形式存储的
- Python中整型是不限制长度的不会超范围溢出
所以为了获得负数(十进制表示)的补码,需要手动将其和十六进制数0xffffffff
进行按位与操作,再交给bin()
进行输出,得到的才是负数的补码表示。