【问题标题】:How does this code work to count number of 1-bits?此代码如何计算 1 位的数量?
【发布时间】:2014-02-03 14:21:01
【问题描述】:

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

【问题讨论】:

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


【解决方案1】:

此算法使用x 作为计算源和内存。首先,想想位掩码是什么:

0x55 = 01010101
0x33 = 00110011
0x0f = 00001111
0xff = 11111111

我们来看一个 8 位的例子:a = 01101010

a & 0x55 = 01000000; (a >> 1) & 0x55 = 00010101 

如果我们将这两个加在一起,我们会得到每两个位对的位数。这个结果最多为0x10,因为掩码每次加法只设置一个位。

现在,我们使用0x33 掩码,记住每个连续的 2 位包含第一次操作的结果。使用这个掩码,我们屏蔽掉连续的 4 位并将它们相加。这个结果最多是0x100。这将继续使用其他掩码,直到我们在x 中得到结果。

在纸上试一试,几步之后就很清楚了。

【讨论】:

  • 我一直在寻找更科学的解决方案。我的意思是,您的回答没有问题,但我仍然不知道位掩码是如何选择的。
  • 掩码选择越来越长的连续位(由于算法处理 32 位整数,因此您需要比我的示例更多的掩码)。你能告诉我你到底有什么不明白的吗?因为对我来说,在纸上完成之后就很明显了。
  • @SilviuBurcea 这是口罩唯一明显的选择,否则你会怎么做?
【解决方案2】:

这是一种分而治之的方法。请注意,第一行将给出后续对的总和,接下来是后续四倍的总和,...最后是位数。

示例(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

【讨论】:

    【解决方案3】:

    为了更容易解释,让我假设一个整数是 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 2 的总和。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 位的数量。

    【讨论】:

      【解决方案4】:

      第一行将整数视为 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 位整数组成的数组。在操作之前,每个加数都是 0、1 或 2(十进制),因为它对应于最后一行的计数。因此,我们知道每个和不会大于 4。由于每个 4 位 int 的最大值为 15(十进制),我们知道这也不会溢出。如上,结果存储回x

      x = (x & (0x33333333)) + ((x >> 2) & (0x33333333));

      我们和上面一样,这次将每对 4 位相加到每组 8 位中。

      x = (x & (0x0f0f0f0f)) + ((x >> 4) & (0x0f0f0f0f));

      然后我们将每对 8 位求和成每对 16 位(x 的上半部分和下半部分)。

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

      我们现在将x 的上半部分和下半部分相加,剩下的32 位值等于x 的值中设置的位数。

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

      这里的关键是每个步骤都会对位进行就地成对计数,直到我们得到它自己的 32 位整数中的总位数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-04-23
        • 1970-01-01
        • 2020-01-25
        • 2015-04-14
        • 1970-01-01
        • 2020-01-17
        • 2011-04-24
        相关资源
        最近更新 更多