IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 二进制中 1 的个数 ——《C/C++ 位运算黑科技 03》 -> 正文阅读

[C++知识库]二进制中 1 的个数 ——《C/C++ 位运算黑科技 03》

二进制中 1 的个数 ——《C/C++ 位运算黑科技 03》

欢迎大家来我的博客逛逛👏:hauhau.cn

原理

计算一个二进制数中 1 的出现次数其实很简单,只需要不断用 v & (v - 1) 移除掉最后一个 1 即可,原理可以参考这篇文章:2 的幂次方 ——《C/C++ 位运算黑科技 02》

上述方法是一个普通的思考方向,下面我会介绍另外一种思路:并行计数器,来计算二进制数中出现的 1

实际上,我们可以将这个数看作是全部由单位的计数器组成,1、0 就代表单个计数器的状态,我们只要合并相邻的计数器即可,这其实也是归并的思想。

代码

inline unsigned count_bits(uint64_t v)
{
    v = (v & 0x5555555555555555) + ((v >> 1) & 0x5555555555555555);
    v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333);
    v = (v & 0x0f0f0f0f0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f0f0f0f0f);
    v = (v & 0x00ff00ff00ff00ff) + ((v >> 8) & 0x00ff00ff00ff00ff);
    v = (v & 0x0000ffff0000ffff) + ((v >> 16) & 0x0000ffff0000ffff);
    v = (v & 0x00000000ffffffff) + ((v >> 32) & 0x00000000ffffffff);
    return v;
}

inline unsigned count_bits(uint32_t v)
{
    v = (v & 0x55555555) + ((v >> 1) & 0x55555555);
    v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
    v = (v & 0x0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f);
    v = (v & 0x00ff00ff) + ((v >> 8) & 0x00ff00ff);
    v = (v & 0x0000ffff) + ((v >> 16) & 0x0000ffff);
    return v;
}

inline unsigned count_bits(uint16_t v)
{
    v = (v & 0x5555) + ((v >> 1) & 0x5555);
    v = (v & 0x3333) + ((v >> 2) & 0x3333);
    v = (v & 0x0f0f) + ((v >> 4) & 0x0f0f);
    v = (v & 0x00ff) + ((v >> 8) & 0x00ff);
    return v;
}

inline unsigned count_bits(uint8_t v)
{
    v = (v & 0x55) + ((v >> 1) & 0x55);
    v = (v & 0x33) + ((v >> 2) & 0x33);
    v = (v & 0x0f) + ((v >> 4) & 0x0f);
    return v;
}

原理剖析

下面以 1110001010011110 作为例子,来解释并行计数器合并的方法:

val1110001010011110
& 0x55550101010101010101
=0100000000010100
val >> 10111000101001111
& 0x55550101010101010101
=0101000101000101

然后两者相加就得到了相邻 2 个计数器的合并计数:1001000101011001,然后我们在以 2 个比特为单位来继续合并计数器

Val1001000101011001
& 0x33330011001100110011
=0001000100010001
Val >> 20010010001010110
& 0x33330011001100110011
=0010000000010010

然后两者相加就得到了相邻 4 个计数器的合并计数:0011000100100011,然后我们在以 4 个比特为单位来继续合并计数器

Val0011000100100011
& 0x0f0f0000111100001111
=0000000100000011
Val >> 40000001100010010
& 0x0f0f0000111100001111
=0000001100000010

然后两者相加就得到了相邻 8 个计数器的合并计数:0000010000000101,然后我们在以 8 个比特为单位来继续合并计数器

Val0000010000000101
&00ff0000000011111111
=0000000000000101
Val >> 80000000000000100
&00ff0000000011111111
=0000000000000100

然后两者相加就得到了相邻 8 个计数器的合并计数:0000000000001001,转换成十进制就是 9,与原数字中的 1 的个数是相同的。

Benchmark

#include "benchmark/benchmark.h"

inline unsigned count_bits(uint64_t v)
{
  v = (v & 0x5555555555555555) + ((v >> 1) & 0x5555555555555555);
  v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333);
  v = (v & 0x0f0f0f0f0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f0f0f0f0f);
  v = (v & 0x00ff00ff00ff00ff) + ((v >> 8) & 0x00ff00ff00ff00ff);
  v = (v & 0x0000ffff0000ffff) + ((v >> 16) & 0x0000ffff0000ffff);
  v = (v & 0x00000000ffffffff) + ((v >> 32) & 0x00000000ffffffff);
  return v;
}

inline unsigned count_bits(uint32_t v)
{
  v = (v & 0x55555555) + ((v >> 1) & 0x55555555);
  v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
  v = (v & 0x0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f);
  v = (v & 0x00ff00ff) + ((v >> 8) & 0x00ff00ff);
  v = (v & 0x0000ffff) + ((v >> 16) & 0x0000ffff);
  return v;
}

inline unsigned count_bits(uint16_t v)
{
  v = (v & 0x5555) + ((v >> 1) & 0x5555);
  v = (v & 0x3333) + ((v >> 2) & 0x3333);
  v = (v & 0x0f0f) + ((v >> 4) & 0x0f0f);
  v = (v & 0x00ff) + ((v >> 8) & 0x00ff);
  return v;
}

inline unsigned count_bits(uint8_t v)
{
  v = (v & 0x55) + ((v >> 1) & 0x55);
  v = (v & 0x33) + ((v >> 2) & 0x33);
  v = (v & 0x0f) + ((v >> 4) & 0x0f);
  return v;
}

static void BM_count_64(benchmark::State &state) {
  for (auto _: state) {
    uint64_t n = UINT64_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

static void BM_count_32(benchmark::State &state) {
  for (auto _: state) {
    uint32_t n = UINT32_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

static void BM_count_16(benchmark::State &state) {
  for (auto _: state) {
    uint16_t n = UINT16_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

static void BM_count_8(benchmark::State &state) {
  for (auto _: state) {
    uint8_t n = UINT8_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

BENCHMARK(BM_count_8);
BENCHMARK(BM_count_16);
BENCHMARK(BM_count_32);
BENCHMARK(BM_count_64);

BENCHMARK_MAIN();

下面是使用 MacBook Air (M1, 2020) 和 Apple clang 13.1.6 得到的结果

/Users/hominsu/CLionProjects/bit-hacks-bench/cmake-build-release-appleclang/bench/count_bits
Unable to determine clock rate from sysctl: hw.cpufrequency: No such file or directory
2022-03-27T14:09:30+08:00
Running /Users/hominsu/CLionProjects/bit-hacks-bench/cmake-build-release-appleclang/bench/count_bits
Run on (8 X 24.1205 MHz CPU s)
CPU Caches:
  L1 Data 64 KiB (x8)
  L1 Instruction 128 KiB (x8)
  L2 Unified 4096 KiB (x2)
Load Average: 2.64, 2.22, 1.79
------------------------------------------------------
Benchmark            Time             CPU   Iterations
------------------------------------------------------
BM_count_8       0.319 ns        0.319 ns   1000000000
BM_count_16      0.321 ns        0.321 ns   1000000000
BM_count_32      0.313 ns        0.313 ns   1000000000
BM_count_64      0.316 ns        0.316 ns   1000000000

下面是使用 i5-9500 和 gcc 8.5.0 (Red Hat 8.5.0-10) 在 CentOS-8-Stream 下得到的结果

/tmp/tmp.CtmwmpTLjC/cmake-build-release-1104/bench/count_bits
2022-03-27T14:10:07+08:00
Running /tmp/tmp.CtmwmpTLjC/cmake-build-release-1104/bench/count_bits
Run on (6 X 4100.35 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x6)
  L1 Instruction 32 KiB (x6)
  L2 Unified 256 KiB (x6)
  L3 Unified 9216 KiB (x1)
Load Average: 0.57, 0.54, 0.51
------------------------------------------------------
Benchmark            Time             CPU   Iterations
------------------------------------------------------
BM_count_8       0.244 ns        0.244 ns   1000000000
BM_count_16      0.246 ns        0.246 ns   1000000000
BM_count_32      0.245 ns        0.244 ns   1000000000
BM_count_64      0.249 ns        0.248 ns   1000000000
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-31 23:45:54  更:2022-03-31 23:47:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:48:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码