我想找到以二进制表示无符号 numpy 整数(或整数数组中的每个元素)所需的位数,就像 python 的
int.bit_length()
所做的那样,但 numpy 似乎没有等效的函数。
例如:
>>> int(0b1000).bit_length()
4
>>> np.uint8(0b1000).bit_length()
AttributeError: 'numpy.uint8' object has no attribute 'bit_length'
谁能帮我找到正确的函数?我当前的方法是将每个数组元素转换为 python int 来查找位长度,为了速度和清晰度,这似乎是一个糟糕的选择:
np.vectorize(lambda np_int: int(np_int).bit_length())
你可以取数组 log2 的上限。
import numpy as np
x = np.random.randint(0, 100, size=30)
x
# returns:
array([92, 7, 53, 24, 85, 53, 78, 52, 99, 91, 79, 40, 82, 34, 18, 26, 20,
7, 47, 38, 78, 50, 15, 12, 54, 3, 91, 82, 22, 90])
np.ceil(np.log2(x)).astype(int)
# returns:
array([7, 3, 6, 5, 7, 6, 7, 6, 7, 7, 7, 6, 7, 6, 5, 5, 5, 3, 6, 6, 7, 6,
4, 4, 6, 2, 7, 7, 5, 7])
试试这个:
np.uint8(0b1000).nbytes*8
最后的 *8 只是每个字节的位数
经典的位操作黑客算法应该很容易适应 numpy:
https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
这是 32 位无符号整数的变体:
def bit_length(v):
r = (v > 0xFFFF) << 4; v >>= r
shift = (v > 0xFF ) << 3; v >>= shift; r |= shift
shift = (v > 0xF ) << 2; v >>= shift; r |= shift
shift = (v > 0x3 ) << 1; v >>= shift; r |= shift
return r | (v >> 1)
考虑到向量化,对于非常大的列表和双精度数默认尾数大小为 2^53 的数字,使用浮点 log2 方法可能会获得更好的性能。 其实并不慢。 Numpy 需要提供 CTZ 或 CLZ 函数,否则这里只发生反前导零或反尾随零。 顺便说一句,浮点 log2 并没有比这些相同的 CPU 类型操作做更多的事情。 所以它可能并不像看起来那么低效。
使用
np.frexp(arr)[1]
的速度比 np.ceil(np.log2(x)).astype(int)
快 4 到 6 倍。
请注意,正如@GregoryMorse 上面所指出的,需要一些额外的工作来保证 64 位输入的正确结果(下面的
bit_length3
)。
import numpy as np
def bit_length1(arr):
# assert arr.max() < (1<<53)
return np.ceil(np.log2(arr)).astype(int)
def bit_length2(arr):
# assert arr.max() < (1<<53)
return np.frexp(arr)[1]
def bit_length3(arr): # 64-bit safe
_, high_exp = np.frexp(arr >> 32)
_, low_exp = np.frexp(arr & 0xFFFFFFFF)
return np.where(high_exp, high_exp + 32, low_exp)
性能结果,通过 https://perfpy.com/868 对 100,000 个元素的数组进行 10 次迭代。