我找到以下代码来计算给定整数的二进制表示中1-bits
的数量。谁能解释它是如何工作的?如何为这样的任务选择位掩码?感谢。
int count_one(int x)
{
x = (x & (0x55555555)) + ((x >> 1) & (0x55555555));
x = (x & (0x33333333)) + ((x >> 2) & (0x33333333));
x = (x & (0x0f0f0f0f)) + ((x >> 4) & (0x0f0f0f0f));
x = (x & (0x00ff00ff)) + ((x >> 8) & (0x00ff00ff));
x = (x & (0x0000ffff)) + ((x >> 16) & (0x0000ffff));
return x;
}
答案 0 :(得分:8)
此算法使用x
作为计算源和内存。首先,考虑一下bitmasks是什么:
0x55 = 01010101
0x33 = 00110011
0x0f = 00001111
0xff = 11111111
让我们看一个8位的例子:a = 01101010
a & 0x55 = 01000000; (a >> 1) & 0x55 = 00010101
如果我们将这两者加在一起,我们得到每两位对的位数。此结果最多为0x10
,因为掩码只为每次添加设置了一个位。
现在,我们使用0x33
掩码,记住每个连续的2位包含第一个操作的结果。使用此掩码,我们屏蔽连续的4位并添加它们。此结果最多为0x100
。这继续使用其他掩码,直到我们得到x
的结果。
在纸上试一试,经过几个步骤就可以了。
答案 1 :(得分:5)
这是一种分而治之的方法。请注意,第一行将为后续对提供总和,接下来是后续四元组的总和,...以及最后的位数。
示例(16位因此请考虑没有最后一行的代码)
1011001111000110
在第一行中,我们将&
取偶数位,奇数位移一位。然后我们添加。
偶数位:
num: 10 11 00 11 11 00 01 10
mask: 01 01 01 01 01 01 01 01
res: 00 01 00 01 01 00 01 00
对于奇数位:
num>>1: 01 01 10 01 11 10 00 11
mask: 01 01 01 01 01 01 01 01
res: 01 01 00 01 01 00 00 01
我们添加这些结果并在后续对中获得总和
01 10 00 10 10 00 01 01
我们用以下面具和相应的轮班重复同样的事情
2nd: 0011 0011 0011 0011
3rd: 0000 1111 0000 1111
4th: 0000 0000 1111 1111
我们得到:
2nd: 0011 0010 0010 0010 // 3 set in the first four, 2 in other quadrupels
3rd: 0000 0101 0000 0100 // 5 bits set in the first eight, 4 in the rest
4th: 0000 0000 0000 1001 // 9 bits in total
答案 2 :(得分:2)
为了使这更容易解释,让我假设一个整数是4位长,每个位表示为1,2,3,4。为了获得1-bits
的数量,您可以先获得1和2的总和以及3和4的总和,然后将这些总和相加。
x & (0x5)
将消除1和3,x & (0xa)
将消除2和4. x & (0x5) + (x & (0xa) >> 1)
将使用1 2位来存储1和2的总和并使用3 4位来存储3和4的总和。x & (0x5) + (x & (0xa) >> 1)
与x & (0x5) + (x >> 1) & (0x5)
相同。
现在我们将1 2和3 4的总和全部存储在x中,我们可以在添加它们之后得到结果。与上面相同,x & (0x3)
将获得3 4的总和,x & (0x12)
将得到1的总和。x & (0x3) + (x & (0x12)) >> 2
将获得最终结果。 x & (0x3) + (x & (0x12)) >> 2
与x & (0x3) + (x >> 2) & (0x3)
相同。
所以你可以看到里面发生的事情。在你的情况下,第一行你可以得到1 2和3 4和5 6的总和,然后继续。在第二行,你得到1 2 3 4和5 6 7 8的总和,然后继续。所以通过这样做5次,你得到所有32位的数量。
答案 3 :(得分:1)
第一行将整数视为16个2位整数的数组,如果设置了2位对中的0位,则表达式的结果为0;如果设置了2位对中的1位,则表达式为1 (01 bin或10 bin),如果设置了2位对的两个位,则为2。这是因为我们考虑x
的每两位中的较低位,并且x
的每两位中的较低位右移一位;将它们加在一起。我们知道在2位对之外不会发生任何进位,因为我们的求和被限制为0或1.然后将结果存储回x
。
x = (x & (0x55555555)) + ((x >> 1) & (0x55555555));
下一行做同样的事情,这次将每2位作为一个单独的整数处理,将它们的总和存储在用于占用的两个加数的每4位中。在此操作之后,整数基本上变为8个4位整数的数组。在操作之前,每个summand是0,1或2(十进制),因为它对应于最后一行的计数。因此我们知道每个总和不会超过4.由于每个4位int的最大值为15(十进制),我们知道这也不会溢出。如上所述,结果将存储回x
。
x = (x & (0x33333333)) + ((x >> 2) & (0x33333333));
我们如上所述,这次将每对4位加到每组8位中。
x = (x & (0x0f0f0f0f)) + ((x >> 4) & (0x0f0f0f0f));
x
的上半部分和下半部分)中的一对8位相加。
x = (x & (0x00ff00ff)) + ((x >> 8) & (0x00ff00ff));
我们现在对x
的上半部分和下半部分求和,并且我们留下32位值,该值等于x
值中设置的位数。
x = (x & (0x0000ffff)) + ((x >> 16) & (0x0000ffff));
这里的关键是每一步都进行一个就位的成对位计数,直到我们留下32位整数的总位数。