在C中的位掩码中找出设置位位置的最佳方法

时间:2013-10-14 17:07:59

标签: c embedded bit-manipulation bitmask bit-masks

识别64位位掩码中所有设置位位置的最佳方法是什么。假设我的位掩码是0xDeadBeefDeadBeef,那么识别其中所有位位置的最佳方法是什么。

long long bit_mask = 0xdeadbeefdeadbeef;
unsigned int bit_pos=0;
while(mask) {
  if((mask&1)==1) {
     printf("Set bit position is:%d \n",bit_pos};
  }
  bit_pos++;
  mask>>=1; 
}

一种方法是遍历它,并检查是否设置了一个位,如果它被设置,返回计数位置并继续循环直到MSB,所以对于64位,我会迭代直到我拥有所有遍历设置位或遍历所有64位,如果设置了MSB,但必须有更好的方法吗?

5 个答案:

答案 0 :(得分:2)

来自Hacker's Delight的算法(书):

int count_bits(long long s)
{
    s = (s&0x5555555555555555L) + ((s>>1)&0x5555555555555555L);
    s = (s&0x3333333333333333L) + ((s>>2)&0x3333333333333333L);
    s = (s&0x0F0F0F0F0F0F0F0FL) + ((s>>4)&0x0F0F0F0F0F0F0F0FL);
    s = (s&0x00FF00FF00FF00FFL) + ((s>>8)&0x00FF00FF00FF00FFL);
    s = (s&0x0000FFFF0000FFFFL) + ((s>>16)&0x0000FFFF0000FFFFL);
    s = (s&0x00000000FFFFFFFFL) + ((s>>32)&0x00000000FFFFFFFFL);

    return (int)s;
}

答案 1 :(得分:1)

除了已经解释过的有点笨拙的黑客之外,还有其他选择。

这假设你有x86(64),SSE4,gcc和使用-msse4开关编译你可以使用:

int CountOnesSSE4(unsigned int x)
{
    return __builtin_popcount(x);
}

这将编译成单个popcnt指令。如果您需要快速代码,您可以在运行时实际检查SSE并使用最佳功能。

如果您希望数字的数量很少,那么这也可能很快(并且总是比通常的移位和比较循环更快):

int CountOnes(unsigned int x)
{
    int cnt = 0;
    while (x) {
        x >>= ffs(x);
        cnt++;
    }
    return cnt;
}

在x86上(即使没有SSE),ffs将编译为单指令(bsf),循环次数将取决于1的数量。

答案 2 :(得分:0)

您可以这样做:

long long bit_mask = 0xdeadbeefdeadbeef;

int i;
for (i = 0; i < (sizeof(long long) * 8); i++) {
    int res = bit_mask & 1;
    printf ("Pos %i is %i\n", i, res);
    bit_mask >>= 1;

}

答案 3 :(得分:0)

这取决于您是希望清晰的代码还是非常快速的结果。我几乎总是在代码中选择清晰度,除非分析告诉我。为清楚起见,您可能会执行以下操作:

int count_bits(long long value) {
    int n = 0;
    while(value) {
        n += (value & 1);
        value >>= 1;
   }
   return n;
}

对于性能,您可能希望从X J的答案中调用count_bits。

int count_bits(long long s)
{
    s = (s&0x5555555555555555L) + ((s>>1)&0x5555555555555555L);
    s = (s&0x3333333333333333L) + ((s>>2)&0x3333333333333333L);
    s = (s&0x0F0F0F0F0F0F0F0FL) + ((s>>4)&0x0F0F0F0F0F0F0F0FL);
    s = (s&0x00FF00FF00FF00FFL) + ((s>>8)&0x00FF00FF00FF00FFL);
    s = (s&0x0000FFFF0000FFFFL) + ((s>>16)&0x0000FFFF0000FFFFL);
    s = (s&0x00000000FFFFFFFFL) + ((s>>32)&0x00000000FFFFFFFFL);

    return (int)s;
}

这取决于你是否想要查看你的代码并对自己说,“是的,这是有意义的”或“我会接受那个人的话”。

我已经在堆栈溢出之前被调用了。有些人不同意。一些非常聪明的人选择复杂而非简单。我相信干净的代码是简单的代码。

如果性能需要它,请使用复杂性。如果没有,请不要。

另外,请考虑代码审查。当有人说“count_bits如何工作?”时,你会说什么?

答案 4 :(得分:0)

如果你正在计算那些你可以使用快速的黑客快乐解决方案,但查找表可以(不总是)更快。而且更容易理解。您可以预先准备一个表,例如深256个项,表示字节值0x00到0xFF

的计数
0, //0x00
1, //0x01
1, //0x02
2, //0x03
1, //0x04
2, //0x05
2, //0x06
3, //0x07
...

构建该表的代码可能会使用每一位方法的慢速步骤。

虽然你可以将更大的数字分成几个字节

count  = table8[number&0xFF]; number>>=8;
count += table8[number&0xFF]; number>>=8;
count += table8[number&0xFF]; number>>=8;
count += table8[number&0xFF]; number>>=8;
...

如果你有更多的内存,你可以通过代表更宽的数字使表更大,一个65536深的表,数字为0x0000到0xFFFF。

count  = table16[number&0xFFFF]; number>>16;
count += table16[number&0xFFFF]; number>>16;
count += table16[number&0xFFFF]; number>>16;
count += table16[number&0xFFFF]; number>>16;

表是一种以牺牲内存消耗为代价使这样的事情变得更快的一般方法。您可以消耗的内存越多,您就可以预先计算(在编译时或编译之前),而不是实时计算。