variable precision SWAR算法
计算二进制形式中1的数量这种问题,在各种刷题网站上比较常见,以往都是选择最笨的遍历方法“蒙混”过关。在了解Redis的过程中接触到了variable precision SWAR算法(以下简称VP-SWAR算法),算法异常简洁,是目前已知的同类方法中最快的。但如果对于位运算不是很熟悉的话,却不一定容易理解,所以有必要记录一下。
下面先看看VP-SWAR算法的完整实现,然后再逐行解释。
public int vpSWAR(int i){
i = (i & 0x55555555) + ((i>>1) & 0x55555555);
i = (i & 0x33333333) + ((i>>2) & 0x33333333);
i = (i & 0x0F0F0F0F) + ((i>>4) & 0x0F0F0F0F);
i = (i * 0x01010101) >> 24;
return i;
}
VP-SWAR算法分为四步,第一步
i = (i & 0x55555555) + ((i>>1) & 0x55555555);
第一步的作用是计算每两位为一组的二进制形式包含1的个数。要理解这句话,我们需要从二进制的角度看看到底发生了什么。首先, 0x55555555 的二进制表示为 0101 0101 0101 0101 0101 0101 0101 0101 ,这个数字的规律是基数位为1,偶数位为0。为简单起见,我们只考虑两位,总共有四种情况,即:
| i | x | i & x 结果 |
| 00 | 01 | 00 |
| 01 | 01 | 01 |
| 10 | 01 | 00 |
| 11 | 01 | 01 |
这里用x代表0b01(0b表示二进制)观察发现, i & (0b01) 是i的基数位对应x的1位,i的偶数位对应着x的0位, i & (0b01) 的结果会将i的偶数位置为0,而基数位保持不变,得到的结果就是i的基数位包含1的个数。 (i >> 1) & 0x55555555 先将i右移一位,也就是将i的基数位对应x的0位,i的偶数位对应着x的1位,然后再与 0x55555555 按位与,计算出来的是i的偶数位包含1的个数。两个计算结果相加就得到i每两位为一组中包含的1的数量,我们最后需要的就是这每两位一组的和。
第二步是在第一步的基础上,计算每四位为一组包含1的个数。按照每2位为一组分组用到了 0x55555555 这个数,那么自然的,按照每4位为一组分组自然就需要 0b0011 这种形式,这就是使用 0x33333333 的原因。理论上, i & (0b0011) 总共有16种情况,但是四位二进制位最多包含4个1,用二进制表示为 0b0100 ,所以经过第一步之后,i最多有5种取值,如下:
| i | x | i & x 结果 |
| 0000 | 0011 | 0000 |
| 0001 | 0011 | 0001 |
| 0010 | 0011 | 0010 |
| 0011 | 0011 | 0011 |
| 0100 | 0011 | 0000 |
观察发现, i & (0b0011) 得到的是i的低两位包含1的个数, (i >> 2) & 0b0011)得到的是i的高两位包含的1的个数,两个结果相加得到每四位包含的1的个数。注意,这里并不是说任何数与 0b0011 按位与得到的都是低两位包含的1的个数,这里的前提是第一步的计算,因为经过第一步计算之后,每两位包含多少个1已经记录了下来,再和 0b0011 按位与才得到正确的结果。例如, 0x0010 & 0x 0011=0x0010 ,但是我们不能说 0x0010 包含两个1,但是如果 0x0010 是经过第一步的计算得来,那才说明 0x0010 记录原始数据低两位有两个1。
第三步在第二步基础上,计算每8位有多少个1,由 0x01 和 0x0011 ,很自然想到 0x00001111 ,其对应的32位的十六进制数就是 0x0F0F0F0F 。
第四步就很有意思了,它不再是计算每16位包含1的个数,而是直接计算32位包含1的个数。对于32位的数来说,可以将其按每8位一组分为4组,分别用ABCD表示,例如 0x01020304 用这种形式表示为:

假设 0x01020304 是经过前三步计算之后得到的结果,那么要计算其总共包含多少个1,只需计算A+B+C+D。而ABCD表示的是不同的位区间范围,不能直接相加,该如何快速计算A+B+C+D的值呢?这里又用到了移位运算,将B、C、D分别左移8位、16位、24位,使其分别与A对齐:

我们发现,将数字i分别左移0位、8位、16位、24位然后相加的结果,就是 i * 0x01010101 ,因为 i + (i << 8) + (i << 16) + (i << 24) = i * (1 + 1 << 8 + 1 << 16 + 1 << 24) = i * 0x01010101 。对于32位数字来说,左移之后超过32位的部分会被舍弃,低位补0,将左移之后得到的四个数字相加,结果的高8位的值就是原32位数包含的1的个数,要得到这个值,只需要将结果右移24位,将值放在低8位即可。
到这里,整个算法就结束了,右移的结果就是1的数量。在Redis中,BITCOUNT命令同时使用了查表法和VP-SWAR这两种方法。当要计算的位数小于128位时,使用查表法,否则使用VP-SWAR算法。其中查表法的做法是,程序先存一个256长度的表,按顺序记录从0-255(即 0b00000000 - 0b11111111) 数中二进制1的个数,然后对于输入参数每8位查一次表。
variable precision SWAR算法的更多相关文章
- variable-precision SWAR算法介绍
BITCOUNT命令是统计一个位数组中非0进制位的数量,数学上称作:”Hanmming Weight“ 目前效率最好的为variable-precision SWAR算法,可以常数时间内计算出多个字节 ...
- variable-precision SWAR算法:计算Hamming Weight
variable-precision SWAR算法:计算Hamming Weight 转自我的Github 最近看书看到了一个计算Hamming Weight的算法,觉得挺巧妙的,纪录一下. Hamm ...
- [算法]从一道题引出variable-precision SWAR算法
苏君君出了一道题,是牛客网上面的: 输入一个int型整数,输出该数二进制表示中1的个数.其中负数用补码表示. 其实这道题并不难,大家很容易想到的解法是转成字符串的思路,即如下所示: public st ...
- redis bitcount variable-precision swar算法
花了不到一周的时间看完了一本reids设计与实现的书,感觉整体的设计有些地方的确很巧妙,各个结构之间联系的非常紧密,但是很简单,逻辑性的没有太多,但是学到了一个bitcount计数1的方法比较巧妙,记 ...
- General Decimal Arithmetic 浮点算法
General Decimal Arithmetic http://speleotrove.com/decimal/ General Decimal Arithmetic [ FAQ | Decima ...
- Redis设计与实现-附加功能
发布与订阅 redis订阅分为精准的频道订阅与模糊匹配的模式订阅: redis将所有频道的订阅关系都保存在服务器状态pubsub_channels字典里,键是频道名,值是一个记录所有订阅此频道的客户端 ...
- IEEE论文格式要求
0.特别提示:本次会议要求各位作者根据审稿意见进行认真修改,然后经过大会主席的检查合格才允许上传IEEE eXpress,主要的目的是为了保证论文集的质量,不让论文格式出现五花八门的情况,确保会议后被 ...
- MATLAB中digits和vpa
digits: DIGITS Set variable precision digits. Digits determines the accuracy of variable precision n ...
- SIMD---AVX系列
AVX全称Advanced Vcetor Extension,是对SSE的后续扩展,主要分为AVX.AVX2.AVX512三种.在目前常见的机器上,大多只支持到AVX系列,因此其他SIMD扩展指令我们 ...
随机推荐
- linux 防火墙基本使用
写在最前面 由于工作后,使用的Linux就是centos7 所以,本文记录是是centos7的防火墙使用. 从 centos7 开始,系统使用 firewall 进行防火墙的默认管理工具. 基本使用 ...
- RocketMQ事务消息学习及刨坑过程
一.背景 MQ组件是系统架构里必不可少的一门利器,设计层面可以降低系统耦合度,高并发场景又可以起到削峰填谷的作用,从单体应用到集群部署方案,再到现在的微服务架构,MQ凭借其优秀的性能和高可靠性,得到了 ...
- web安全之php中常见的INI文件配置
php.ini 在 PHP 启动时被读取.对于服务器模块版本的 PHP,仅在 web 服务器启动时读取 一次.对于 CGI 和 CLI 版本,每次调用都会读取. * Apache web 服务器在启动 ...
- 数据挖掘:python数据清洗cvs里面带中文字符
数据清洗,使用python数据清洗cvs里面带中文字符,意图是用字典对应中文字符,即key值是中文字符,value值是index,自增即可:利用字典数据结构没有重复key值的特性,把中文字符映射到了数 ...
- php数组怎么根据其中的一个值进行排序?解决方法
有一个数组如下 我想对其进行一个排序,按照其“sort”的值的大小进行排序,3->4->7. 解决方法如下: 1.首先介绍php的两个方法 方法一:array_column() 返回输入数 ...
- boost::VS2017下编译和配置boost库
环境: win10 vs2017 v141 1.下载 boost_1_70_0.zip. 2.以管理员方式打开 3. bootstrap.bat 4.编译64位库 b2.exe stage -- ...
- nginx::certbot制作免费证书
环境 Ubuntu8.04apt-get update apt-get install software-properties-common add-apt-repository ppa:certbo ...
- ESP8266开发之旅 进阶篇⑤ 代码规范 —— 像写文章一样优美
1.前言 之前,一直在跟大伙分享怎么去玩蓝牙模块,怎么去玩wifi模块,怎么去玩json,然后有很多小伙伴就留言各种问题或者说直接怼他的代码过来让我看,然后我就一脸懵逼(代码中到处各种abcd ...
- Java基础(二十)集合(2)Collection接口
1.Collection接口通常不被直接使用.但是Collection接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作,因为List接口和Set接口都实现了Collection接口,所以 ...
- QQ聊天记录分析
今天我们用R语言来处理一下.我们会用到一下技术:. (1)正则表达式 (2)词频统计 (3)文本可视化 (4)ggplot2绘图 (5)中文分词 一.数据处理 首先我们要讲QQ聊天记录导出成txt文件 ...