variable-precision SWAR算法解析

本文介绍了在阅读redis实现时遇到的variable-precision SWAR算法。由于中文资料稀缺,作者通过翻译Stack Overflow上的解答来解析该算法,帮助读者理解其工作原理。算法的核心在于利用位操作进行二进制计算,避免高位对低位的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在看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 >> 1) & 0x55555555),将i右移一位,并将所有偶数位设置为0.(等效的,我们也可以通过& 0xAAAAAAAA将所有奇数位设置成0,然后再将结果右移1位)为了方便起见,我们将这个中间值命名为j。
当我们将中间值j从原始值i中减去会发生什么?那让我们来看看如果i只有两位是什么情况。
    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-j的最低两位仍然如上表所示,三四位,五六位也是一个道理,等等。需要注意的是:
  • 由于& 0x55555555的巧妙用法,尽管>> 1,i-j的最低两位不会被i的第三位或者更高的位影响。
  • 由于j的最低两位永远不可能在比i的最低两位大。这个减法永远不会向i的第三位借位,因此:对于i-j来说,i的最低两位不会影响i的第三位或者更高位。
实际上这一行就是将32位数组分为16个两位为单位的组,每组分别计算1出现的次数。

第二行:

i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
与第一行对比,这一行非常的简单。首先,来看一下0x33333333的二进制表示:
0x33333333 = 0b00110011001100110011001100110011
i & 0x33333333的目的是以4位为分组取四位中的后两位。而(i >> 2) & 0x33333333在把i右移两位后做同样的工作。然后把它们结果加起来。
因此,实际上,这行做的工作就是将最低的两位1出现的次数和最低三四位的1出现的次数相加,得到最低四位的1出现的次数。同样的对于输入的8个四位分组(=16进制数)都是一样的。

第三行:

return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
(i + (i >> 4)) & 0x0F0F0F0F除了这次是用临近的4位1出现的次数系相加,得到8位为一组的1出现的次数,以外原理跟前一行一样。(和上一行有所不同的是,我们可以把&去掉,因为我们知道原始输入8位不可能出现超过8个1因此二进制值不会超过4位。)
现在我们有一个三十二位数,由四个字节组成,每个字节保存着原始输入中为1的位的数量。(我们把它们称作A,B,C和D。)那么为什么我们用0x01010101乘以这个值(命名为k)?

由于:

0x01010101 = (1 << 24) + (1 << 16) + (1 << 8) + 1

可得:

k * 0x01010101 = (k << 24) + (k << 16) + (k << 8) + k
因此,最高位总和就是最终的结果,如下图所示:



k * 0x01010101最高位就是原始输入的1出现次数的最终结果。>> 24只是简单的将最高位的值移到最低位。


另一种更好理解的写法:

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;
}






评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值