最近在看redis的实现时看到了用于二进制位计算的variable-precision SWAR算法,书上解释一笔带过,不是很理解,在百度上也没有搜到相关中文的资料。最后还是在google上搜到了Stack Overflow上一个大神的解释(http://stackoverflow.com/questions/22081738/how-variable-precision-swar-algorithm-works),解释的很清楚,一看就懂了。鉴于网上中文资料不多,我简单翻译了一下,希望对以后的朋友有帮助。
int SWAR(unsigned int i)
{
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
下面逐行解释一下:
i = i - ((i >> 1) & 0x55555555);
0x55555555二进制的标识方式如下:
0x55555555 = 0b01010101010101010101010101010101
可以看到的规律是,奇数位为1,偶数位为0。
i j i - j
----------------------------------
0 = 0b00 0 = 0b00 0 = 0b00
1 = 0b01 0 = 0b00 1 = 0b01
2 = 0b10 1 = 0b01 1 = 0b01
3 = 0b11 1 = 0b01 2 = 0b10
最后的结论就是i-j的十进制结果就是位数组中1出现的次数。
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
与第一行对比,这一行非常的简单。首先,来看一下0x33333333的二进制表示:
0x33333333 = 0b00110011001100110011001100110011
i & 0x33333333的目的是以4位为分组取四位中的后两位。而(i >> 2) & 0x33333333在把i右移两位后做同样的工作。然后把它们结果加起来。
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
(i + (i >> 4)) & 0x0F0F0F0F除了这次是用临近的4位1出现的次数系相加,得到8位为一组的1出现的次数,以外原理跟前一行一样。(和上一行有所不同的是,我们可以把&去掉,因为我们知道原始输入8位不可能出现超过8个1因此二进制值不会超过4位。)
由于:
0x01010101 = (1 << 24) + (1 << 16) + (1 << 8) + 1
可得:
k * 0x01010101 = (k << 24) + (k << 16) + (k << 8) + k
因此,最高位总和就是最终的结果,如下图所示:
int SWAR(unsigned int i)
{
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f);
i = (i*(0x01010101) >> 24)
return i;
}