这段代码如何计算1位数?

时间:2014-01-15 07:59:06

标签: c++ c algorithm bit-manipulation counting

我找到以下代码来计算给定整数的二进制表示中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;
}

4 个答案:

答案 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)) >> 2x & (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));

然后,我们将每对16位(x的上半部分和下半部分)中的一对8位相加。

x = (x & (0x00ff00ff)) + ((x >> 8) & (0x00ff00ff));

我们现在对x的上半部分和下半部分求和,并且我们留下32位值,该值等于x值中设置的位数。

x = (x & (0x0000ffff)) + ((x >> 16) & (0x0000ffff));

这里的关键是每一步都进行一个就位的成对位计数,直到我们留下32位整数的总位数。