有效的逐位运算,用于计数位或查找最右边|最左边的位

Efficient bitwise operations for counting bits or find the right|left most ones

本文关键字:查找 右边 位或 左边 运算 用于 有效      更新时间:2023-10-16

给定一个无符号的int,我必须实现以下操作:

  1. 计数设置为1的位数
  2. 查找最左边1位的索引
  3. 找到最右边的索引1位

(操作不应依赖于体系结构)。

我已经使用逐位移位完成了这项工作,但我必须遍历几乎所有的位(es.32)。例如,计数1:

unsigned int number= ...;
while(number != 0){
    if ((number & 0x01) != 0)
        ++count;
    number >>=1;
}

其他操作类似。

所以我的问题是:有没有更快的方法可以做到这一点?

如果您想要最快的方式,则需要使用不可移植的方法。

Windows/MSVC:

  • _BitScanForward()
  • _BitScanReverse()
  • __popcnt()

GCC:

  • __内置_ffs()
  • __内置_ctz()
  • __内置_clz()
  • __内置_popcount()

这些通常直接映射到本机硬件指令。所以它不会比这些快多少。

但由于它们没有C/C++功能,因此只能通过编译器内部函数访问。

看看ffs(3),ffsl(3)、fls(3。

ffs()和ffsl()函数查找i中的第一个位集(从最低有效位开始),并返回该位的索引。

函数fls()和flsl()查找i中设置的最后一个位,并返回该位的索引。

您可能也对比特串(3)感兴趣。

引用http://graphics.stanford.edu/~seander/bithacks.html

对32位整数v中的位进行计数的最佳方法如下:

unsigned int v; // count bits set in this (32-bit value)
unsigned int c; // store the total here
v = v - ((v >> 1) & 0x55555555);                    // reuse input as temporary
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);     // temp
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count

最佳比特计数方法只需要12次操作,这与查找表方法相同,但避免了表的内存和潜在的缓存未命中。它是上面纯并行方法和早期使用乘法的方法(在关于用64位指令计数位的部分中)的混合,尽管它不使用64位指令。字节中设置的位的计数是并行进行的,并且通过乘以0x1010101并右移24位来计算字节中设置位的总和。

数字x的"最右边的1位"由给出

pos(1st '1') = log_2(x XOR (x-1) + 1) - 1

例如:

x               = 1100101000;
x-1             = 1100100111;
x XOR (x-1)     = 0000001111;
x XOR (x-1) + 1 = 0000010000;

最后一行的base2 log会给出正确的位置+1。因此,从日志结果中减去1,您将获得最正确的"1"位。

对于最右边的"0"位,您可以使用

pos(1st '0') = log_2(x XOR (x+1) + 1) - 1

使用C++20,这些操作可以通过新添加的头<bit>轻松完成。

  1. popcount
  2. countl_one
  3. countr_one

然后,它们中的每一个都将调用编译器特定的函数,如__popcnt()__builtin_popcount()

对于最右边的位简单ans

第一种方法

unsigned int getFirstSetBit(int n){
return log2(n & -n) + 1; 

}

第二种方法

unsigned int getFirstSetBit(int n){
return ffs(n);   

}