【问题标题】:How does this bit manipulation work in Java?这种位操作在 Java 中是如何工作的?
【发布时间】:2012-06-04 07:43:14
【问题描述】:

我正在研究 Java 如何计算 int 的位集。
我脑子里有这样简单的事情(我认为这是正确的):

public static int bitCount(int number){  
        final int MASK = 0x1;  
        int count = 0;  

        for(int i = 0; i < 32; i++){  
            if(((number >>> i) & MASK) == MASK){  
                count++;  
            }  
        }  
        return count;  
    }  

相反,我找到了一种我完全不知道在做什么的方法(对我来说似乎很神奇):

 i = i - ((i >>> 1) & 0x55555555);  
 i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);  
 i = (i + (i >>> 4)) & 0x0f0f0f0f;  
 i = i + (i >>> 8);  
 i = i + (i >>> 16);  
 return i & 0x3f;  

有人可以帮助理解它的作用以及为什么像我最初想到的那样简单的功能是/可能是坏的吗?

【问题讨论】:

  • 你发的算法可以在书["Hacker's Delight][1]第5章(Counting Bits)中找到,是一种分而治之的方法[1]:hackersdelight.org
  • 除非你有一些超级对性能至关重要的代码,否则我随时都会采用你的 bitCount() 方法

标签: java integer bit-manipulation


【解决方案1】:

当然

i = i - ((i >>> 1) & 0x55555555);

5的位模式是0101(四位),所以掩码是位模式01的16次重复。该行计算数字的每个两位对中的位数。如果考虑两位对,(i &gt;&gt;&gt; 1) &amp; 0x1 在低位获得高位。现在,有四种可能的两位模式

00 ~> 00 - 00 = 00
01 ~> 01 - 00 = 01
10 ~> 10 - 01 = 01
11 ~> 11 - 01 = 10

现在每个两位对都包含原始在这些位置的位数。

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

接下来,我们计算每个四位组中的位(也称为半字节)。通过用0x3 = 0011(b) 屏蔽一个半字节,我们得到了半字节低位两位的位数,(i &gt;&gt;&gt; 2) &amp; 0x3 获得了半字节高位两位的计数。现在添加了这些计数。由于每个计数最多为 2,因此总和最多为 4,因此不会离开半字节。

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

现在计算每个八位字节中的位。每个半字节都包含在该位置原始设置的位数。右移四位将计数从每个位置的高位半字节移动到低位半字节,这些相加。然后我们还将计数从低位半字节移动到相邻八位字节的高阶半字节,这被&amp; 0x0f0f0f0f 屏蔽掉了。由于每个八位字节最多可以设置八位,因此计数不会离开八位字节的低位半字节。

i = i + (i >>> 8);

现在我们添加相邻八位字节对的计数。

i = i + (i >>> 16);

现在我们将高位两个八位字节和低位两个八位字节的计数相加。

return i & 0x3f;

最后,除了最低阶八位字节之外的所有八位字节都被屏蔽掉,因为高阶八位字节仍然包含中间计数。

您的简单实现可能被认为不好的原因是性能。复杂的 bit-hack 使用更少的操作并且没有分支,所以它更快。然而,犯错要容易得多。

另一种计算int(或long)中设置位的巧妙方法是

public static int bitCount(int n) {
    int count = 0;
    while(n != 0) {
        ++count;
        n = n & (n-1);
    }
    return count;
}

这是因为n = n &amp; (n-1) 清除了n 中的最后一个设置位,而其他所有内容都保持不变。如果n的位模式以

结尾
...100...0

n-1 的位模式是

...011...1

n 中最后一位之前的位相同。在 Java 中,这保证也适用于负数,因为整数溢出具有环绕语义,所以如果n = Integer.MIN_VALUE,位模式为100...0n - 1 变为Integer.MAX_VALUE,位模式为011...1

【讨论】:

  • 谢谢。我还在研究你的答案。一个问题。循环和执行n&amp;(n-1) 的第二个示例如何比我在OP 中的循环更好?
  • 更好的是,它只执行循环的bitCount(n) 迭代,而无论设置了多少位,您的迭代都执行 32 次。嗯,嗯,如果您以数百万次设置的数位来计算位数,那么性能上的差异是可以测量的。 (换句话说,没有真正的理由使用它而不是您的;如果差异很重要并且您在硬件上没有popcount 指令,您将使用 bit-hack 或查找表 [比如说一个字节中的位数],无论哪种分析显示更快。这只是整洁,IMO。)
【解决方案2】:

cool 方法仅适用于计算机具有有限范围的 int 值。 对于无限范围(如 BigInteger),它不会那么容易工作(以及其他很酷的位算法),因为您需要在计算之前知道所有需要的掩码。

无论如何,您都可以通过以下方式阅读它的工作原理: http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel

它在本章的底部。

【讨论】:

    猜你喜欢
    • 2010-11-06
    • 1970-01-01
    • 1970-01-01
    • 2011-11-02
    • 1970-01-01
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    • 2013-04-13
    相关资源
    最近更新 更多