位运算直接操作二进制数的每一位,不依赖数字的十进制整体值。NumPy 把这类操作封装成矢量化函数,能直接作用于整个数组,底层由 C 实现,因此在处理大规模数据时通常比 Python 原生循环逐元素做位运算更高效。NumPy 位运算函数均以 bitwise_ 开头,同时也支持与 Python 原生位运算符对应的简写形式。
一、NumPy 位运算函数与运算符总览
按位与:numpy.bitwise_and(x1, x2),对应运算符 &,两数对应位均为 1 时结果为 1,否则为 0。
按位或:numpy.bitwise_or(x1, x2),对应运算符 |,两数对应位有一个为 1 时结果为 1,否则为 0。
按位异或:numpy.bitwise_xor(x1, x2),对应运算符 ^,两数对应位不同时结果为 1,相同时为 0。
按位取反:numpy.invert(x),对应运算符 ~,对数字每一位取反;有符号数需要结合补码规则理解。
左移:numpy.left_shift(x1, x2),对应运算符 <<,将 x1 的二进制位向左移动 x2 位,右侧补 0。
右移:numpy.right_shift(x1, x2),对应运算符 >>,将 x1 的二进制位向右移动 x2 位,左侧根据符号位补 0 或 1,正数补 0。
二、按位与 bitwise_and 与 & 的用法
按位与的规则是:1 & 1 = 1,1 & 0 = 0,0 & 1 = 0,0 & 0 = 0。
- import numpy as np
- # 单个整数运算
- a, b = 13, 17
- print(f"13 的二进制:{bin(a)},17 的二进制:{bin(b)}") # 0b1101 0b10001
- print("按位与结果:", np.bitwise_and(a, b)) # 等价于 13 & 17,输出 1
- # 数组运算(矢量化)
- arr1 = np.array([13, 25], dtype=int)
- arr2 = np.array([17, 10], dtype=int)
- print("数组按位与:", arr1 & arr2) # 输出 [1 8]
复制代码
13 的二进制为 01101,17 的二进制为 10001。按位对齐后逐位计算,结果为 00001,即十进制的 1。数组运算中,[13, 25] 与 [17, 10] 逐元素按位与,得到 [1, 8],这也是 NumPy 矢量化能力的直接体现。
三、按位或 bitwise_or 与 | 的用法
按位或的规则是:1 | 1 = 1,1 | 0 = 1,0 | 1 = 1,0 | 0 = 0。
- import numpy as np
- a, b = 13, 17
- print(f"13 的二进制:{bin(a)},17 的二进制:{bin(b)}") # 0b1101 0b10001
- print("按位或结果:", np.bitwise_or(a, b)) # 等价于 13 | 17,输出 29
复制代码
13 | 17 的计算过程为:01101 与 10001 逐位或,得到 11101,即十进制 29。
四、按位异或 bitwise_xor 与 ^ 的用法
按位异或的规则是:两数对应位不同时结果为 1,相同时为 0。它同样适用于布尔数组,在布尔值上等价于逻辑异或,True 视为 1,False 视为 0。
- import numpy as np
- arr1 = np.array([True, False, True], dtype=bool)
- arr2 = np.array([False, True, False], dtype=bool)
- # 函数与运算符等价
- result1 = np.bitwise_xor(arr1, arr2)
- result2 = arr1 ^ arr2
- print("异或结果:", result1) # 输出 [ True True True]
- print("结果一致:", np.array_equal(result1, result2)) # 输出 True
复制代码
上面两个布尔数组逐元素异或后,结果均为 True,并且函数调用与运算符写法的结果完全一致。
五、按位取反 invert 与 ~ 的用法
按位取反对数字的每一位执行 0 变 1、1 变 0。需要注意的是,结果受数组 dtype 影响:无符号整数直接逐位取反;有符号整数则要按二进制补码规则解释,最高位为符号位,0 表示正,1 表示负。
无符号整数取反示例:
- import numpy as np
- # uint8 类型(0-255,8位二进制)
- x = np.array([13], dtype=np.uint8)
- x_invert = np.invert(x)
- print(f"13 的 8 位二进制:{np.binary_repr(13, width=8)}") # 00001101
- print(f"取反后的值:{x_invert[0]}") # 242
- print(f"242 的 8 位二进制:{np.binary_repr(242, width=8)}") # 11110010
复制代码
uint8 的 13 表示为 00001101,逐位取反后为 11110010,即十进制的 242。
有符号整数取反示例:
- import numpy as np
- x = 5 # 默认 int 类型(32位)
- x_invert = ~x
- print("5 的二进制(简化):00000101")
- print("~5 的二进制(补码):11111010")
- print(f"~5 的十进制:{x_invert}") # 输出 -6
复制代码
以 ~1 为例说明补码计算步骤:1 的 8 位有符号原码为 00000001;逐位取反后为 11111110,此时符号位为 1,代表负数;再按补码规则处理,除符号位外取反加 1:10000001 + 1 = 10000010;转换为十进制即 -2。
六、左移 left_shift 与 << 的用法
左移将数字的二进制位向左移动指定位数,右侧用 0 填充,等价于乘以 2 的 n 次方,n 为移位数。
- import numpy as np
- x = 10 # 二进制:00001010
- shifted = np.left_shift(x, 2) # 等价于 10 << 2
- print(f"10 左移 2 位结果:{shifted}") # 输出 40
- print(f"40 的二进制:{np.binary_repr(40, width=8)}") # 00101000
复制代码
10 的二进制为 00001010,左移 2 位后变为 00101000,即 40,对应数学运算 10 × 2² = 40。
七、右移 right_shift 与 >> 的用法
右移将数字的二进制位向右移动指定位数,左侧根据符号位补 0 或 1,正数补 0,等价于整除 2 的 n 次方。
- import numpy as np
- x = 40 # 二进制:00101000
- shifted = np.right_shift(x, 2) # 等价于 40 >> 2
- print(f"40 右移 2 位结果:{shifted}") # 输出 10
- print(f"10 的二进制:{np.binary_repr(10, width=8)}") # 00001010
复制代码
40 的二进制为 00101000,右移 2 位后变为 00001010,即 10,对应数学运算 40 ÷ 2² = 10。
八、运算符使用注意事项
优先级问题:位运算符的优先级低于算术运算符,计算时建议加括号。例如 10 & 1 + 2 会先算 1 + 2 得到 3,再算 10 & 3,结果为 2;如果写成 (10 & 1) + 2,求值顺序不同,虽然该例结果碰巧也是 2,但不应依赖这种巧合。
数组兼容性:参与运算的数组需要满足 NumPy 的广播规则,即形状一致或可以广播。
数据类型影响:取反运算的结果受数组 dtype 影响,无符号类型与有符号类型的表现不同,实际使用时建议明确指定类型,避免歧义。
九、典型应用场景
底层数据处理:操作二进制文件中的字节数据,如图片、音频等。
状态标志位控制:用单个整数的不同位表示多个状态,例如用 0b101 表示 3 个状态的组合。
性能优化:用左移、右移替代乘法和除法,在部分场景下速度更快。
加密与校验:基于异或等运算实现简单的数据加密或校验,如 CRC 校验。
十、总结
NumPy 位运算围绕按位与、按位或、按位异或、按位取反、左移和右移展开,函数形式与运算符形式基本一一对应。实际编码时重点注意三点:位运算优先级低于算术运算,数组需要满足广播规则,取反结果受 dtype 影响。掌握这些细节后,就可以把位运算用于数组级二进制处理、状态标志管理和性能优化等场景。 |