Python自学之路—位运算

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)

按位与:&

nm 对应位都是1的时候才输出1,其他情况全是0:

# 5 的补码 : 00 00 01 01
# 6 的补码 : 00 00 01 10
#  5 & 6  : 00 00 01 00 : 4

按位或:|

nm 对应位只要有一个1就输出1,其他情况输出0:

# 5 的补码 : 00 00 01 01
# 6 的补码 : 00 00 01 10
#  5 | 6  : 00 00 01 11 : 7

按位异或:^

只要 nm 对应位不一样就输出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()进行输出,得到的才是负数的补码表示。

上一篇:详解:智能医学影像分析的前沿与挑战 | 硬创公开课


下一篇:AI公开课:19.04.17杨松帆—好未来AI Lab负责人《为人工智能时代打造一个AI老师》课堂笔记以及个人感悟