我知道之前已经问过这个问题,但我正在查看列出here的这个特定解决方案:
int BitCount(unsigned int u)
{
unsigned int uCount;
uCount = u - ((u >> 1) & 033333333333) - ((u >> 2) & 011111111111);
return ((uCount + (uCount >> 3)) & 030707070707) % 63;
}
它是如何运作的?
这里有什么警告吗?
理论上可以在恒定时间内找到答案吗?我的意思是,我们实际上不是有遍历要计数的位吗?
答案 0 :(得分:8)
无符号32位整数u
可以这样写:
u = a31 * 231 + a30 * 230 + ... + a0 * 20
我们想要a31 + a30 + ... + a0
。
让我们比较u >> k
:
u >> 0 = a31 * 231 + a30 * 230 + ... + a1 * 21 + a0 * 20 u >> 1 = a31 * 230 + a30 * 229 + ... + a1 * 20 u >> 2 = a31 * 229 + a30 * 228 + ... ... u >> 29 = a31 * 22 + a29 * 21 + ... u >> 30 = a31 * 21 + a30 * 20 u >> 31 = a31 * 20
我们将通过以下公式计算比特数:
u >> 0 - u >> 1 - u >> 2 - ... - u >> 31 = p
让我们看看为什么会这样:
u >> 0 - u >> 1 - u >> 2 - ... - u >> 31
= u >> 0 - (u >> 1 + u >> 2 + ... + u >> 31)
= u - q
q
的价值是多少?让我们一点一点地计算它,看看上面u >> k
的值。对于a31
,它是:
a31 * 230 + a31 * 229 + ... = a31 * (230 + 229 + ...) = a31 * (231 - 1)
或a30
:
a30 * 229 + a30 * 228 + ... = a30 * (229 + 228 + ...) = a30 * (230 - 1)
我们发现:q = a31 * (231 - 1) + a30 * (230 - 1) + ...
因此
u - q = a31 * 231 - a31 * (231 - 1) + ... = a31 + a30 + ... + a0
这个算法从做同样的事情开始,但是以3比特的块为单位:
u >> 0 = AaBbbCccDddEeeFffGggHhhIiiJjjKkk (each letter is a bit)
u >> 1 & 033333333333 = A Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk (blank = zero)
u >> 2 & 011111111111 = B C D E F G H I J K
根据上述算法的不同,uCount
中的每个八位字节都包含u
中相应八位字节中设置的位数。
uCount = αβγδεζηθικλ (each greek letter is an octet)
uCount >> 3 = αβγδεζηθικ
所以uCount + (uCount >> 3)
是(λ+κ) * 20 + (κ+ι) * 23 + (ι+θ) * 26 + ...
通过与0o30707070707
进行AND运算,我们将每隔一个八位字节屏蔽掉,这样我们只计算每对一次:
r = (λ+κ) * 20 + (ι+θ) * 26 + (η+ζ) * 212 + ... = (λ+κ) * 640 + (ι+θ) * 641 + (η+ζ) * 642 + ...
这是一个base-64号码,我们想要总结64位数字来获得α+β+γ+δ+ε+ζ+η+θ+ι+κ+λ
,这是我们的最终结果。为此,我们计算其base-64 digital root:知道结果永远不会大于32,我们只需将数字模数为63.
答案 1 :(得分:5)
因此,检查一位掩码并为目标值中的每个位移位的解决方案确实是O(1)
(例如,常量为32的位置)。
答案 2 :(得分:5)
执行此操作的最快方法是使用popcnt指令。您通常可以通过compiler intrinsic访问它。您的解决方案在缺少此指令的平台上非常有用。
答案 3 :(得分:1)
Counting bits set, in parallel显示了这是如何完成的。该方法可用于8位,16位,32位,64位,128位等位字,但计算中使用的常数会发生变化。
当我们说这个操作是O(1)时,我们的意思是它可以在恒定时间内完成而不管字大小。计算天真的方式是O(n)in位数。
实际上,当处理器本身可以使用字大小时,这只是O(1)。
至于它如何运作,它使用“魔术数字”。有关说明,请参阅this newsgroup post。