[转]求整数中比特为1的二进制位数

好几次在CSDN上看到别人讨论如何求出一个整数的二进制表示中状态为1的比特位数。今天写了个程序把从网上看来的加上自己想出来的共有5种方法测试了一下,觉得好玩,就写了这篇博客。

首先简单介绍一下这五种方法。

第一种:最简单的,通过移位操作逐位测试并计数,不妨称为“逐位测试法”;

第二种:注意到对于“单比特二进制”而言,其状态与数值“相同”。即对于单个比特的“数”而言,为0即表示数值0,“全部为1”即表示数值1(注意多比特数值显然没有这个特点,比如一个字节有8个比特,当8个比特全为1时并不代表整数8,而代表255)。利用这一特点,从单个比特开始,以相邻的一位、两位、四位、八位和十六位为分组,一组一组地相加并逐步累计,最终得出结果;不妨称为“分组统计法”;

第三种:注意到一个整数减1的会把最后一位1变成0,而其后的0全变成1,利用这一特点,把一个数不断与它减1后的结果做“按位与”,直到它变成0,那么循环的次数便是其状态为1的比特位数。不妨称之为“循环减一相与法”;

第四种:考虚到多数处理器都提供“找到第一个为1的比特位”这种指令,在我的PC上直接调用X86处理器的BSF或BSR指令,每次直接找到第一个不为0的位并消掉,直到该数变成0,那么循环的次数即为状态为1的比特位数。这种不妨称为“汇编嵌入法”;

第五种,一个字节一共有256种状态,将每一个取值所对应的0比特位数的事先写在程序中(注意这些数值是有规律的),也耗不了太多内存,然后程序运行的时候,把整数的四个字节拆开逐字节查表并相加,这种可称为“查表法”。

以下是程序。程序中对应以上五种方法的函数分别命名为f1到f5。另外还有三个函数,correctness_test通过几个简单而又特殊的取值测试各个函数的正确性,相当于一个小单元测试;performance_test则分别将这5个函数作用于一亿个随机整数同进行性能测试;prepare_test_data则是准备1亿个随机整数的程序(这个函数实际并没有为测试数据提供足够的随机性,但这一点对性能测试的结果应该没有太大影响)

#include #include #include using namespace std; int f1(unsigned int num); int f2(unsigned int num); int f3(unsigned int num); int f4(unsigned int num); int f5(unsigned int num); void correctness_test(); void performance_test(); void prepare_test_data(unsigned int data[], int size); int main() { correctness_test(); performance_test(); return 0; } int f1(unsigned int num) { int count = 0; while(num) { if(num & 1) ++count; num >>= 1; } return count; } int f2(unsigned int num) { const unsigned int M1 = 0x55555555; const unsigned int M2 = 0x33333333; const unsigned int M4 = 0x0f0f0f0f; const unsigned int M8 = 0x00ff00ff; const unsigned int M16 = 0x0000ffff; num = (num & M1) + ((num >> 1) & M1); num = (num & M2) + ((num >> 2) & M2); num = (num & M4) + ((num >> 4) & M4); num = (num & M8) + ((num >> 8) & M8); num = (num & M16) + ((num >> 16) & M16); return num; } int f3(unsigned int num) { int count = 0; while(num) { num &= (num - 1); ++count; } return count; } int f4(unsigned int num) { int count = 0; while(num) { int n; __asm { bsr eax, num mov n, eax } ++count; num ^= (1 << n); } return count; } int f5(unsigned int num) { static const signed char TABLE[256] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 }; unsigned char* p = reinterpret_cast(&num); int count = 0; while(p != reinterpret_cast(&num + 1)) { count += TABLE[*p++]; } return count; } void correctness_test() { unsigned int test_data[] = {0, 1, 2, 3, 0x01234567, 0x89abcdef, 0xffffffff}; unsigned int corect_result[] = {0, 1, 1, 2, 12, 20, 32}; int (*fn[])(unsigned int) = {f1, f2, f3, f4, f5}; for(int i = 0; i < sizeof(fn) / sizeof(*fn); ++i) { for(int j = 0; j < sizeof(test_data) / sizeof(*test_data); ++j) { if(fn[i](test_data[j]) != corect_result[j]) { cout << “f” << (i + 1) << " failed!" << endl; exit(-1); } } } cout << “All methods passed correctness test.” << endl; } void performance_test() { const int TEST_DATA_SIZE = 100000000; unsigned int* test_data = new unsigned int[TEST_DATA_SIZE]; prepare_test_data(test_data, TEST_DATA_SIZE); int (*fn[])(unsigned int) = {f1, f2, f3, f4, f5}; for(int i = 0; i < sizeof(fn) / sizeof(*fn); ++i) { clock_t start = clock(); for(int j = 0; j < TEST_DATA_SIZE; ++j) { fn[i](test_data[j]); } int ticks = clock() - start; double seconds = ticks * 1.0 / CLOCKS_PER_SEC; cout << “f” << (i + 1) << " consumed " << seconds << " seconds." << endl; } delete[] test_data; } void prepare_test_data(unsigned int* data, int len) { srand(0); for(int i = 0; i < len; ++i) { data[i] = static_cast(rand() * 1.0 / RAND_MAX * 0xffffffff); } }

在我的机器上(AMD Phenom 8560处理器,Windows XP SP2),使用Visual C++ 2008 Express Edition编译并运行(Release版),某一次得到的输出如下:

All methods passed correctness test.

f1 consumed 14.156 seconds.

f2 consumed 1.032 seconds.

f3 consumed 4.656 seconds.

f4 consumed 13.687 seconds.

f5 consumed 1.422 seconds.

从结果来看,最慢的是第一种“逐位测试法”,最快的是第二种“分组统计法”。两者相差近14倍!

第三种“循环减一相与法”表现也很不错,虽然跟最快的相比逊色很多,但比最慢的强多了;

第四种“汇编嵌入法”,表面上看,其复杂度是跟数值中1的位数相关,这一点与方法三一样。而不像方法一那样复杂度跟整个数的位数有关。但其表现并不令人满意,结果几乎跟方法一一样差,而无法跟方法三相比。查了一下指令手册,发现BSR指令并不是一条固定周期的指令,作用于32位整数时,快的时候它只需几个CPU时钟周期,慢的时候需要40几个时钟周期,我想它极有可能是在CPU内部通过类似于“逐位查找”的微命令实现的。

第五种“查表法”的表现倒让人相当满意,性能逼近方法二。注意我只用了基于字节编码的表,如果实际使用中需要这种运算,我们完全可以构造一个基于unsigned short编码的表,这样一个表将占用64K内存,在现代PC上仍属小菜一碟,而每个32位数只需要把前后两半查表的结果一加即可。那样一来,其性能会不会比方法二还要强呢?有兴趣的朋友可以试试。:P

最后,或许有朋友会问:第四种方法中既然采用嵌入汇编,为何不把整个函数都用汇编来写呢?那样或许效率还会好一些。但那对其它几种方法来说是不公平的,因为所有的方法都可以改用汇编来写。所以,在这个函数中我只是把依赖于特定处理器(X86)、且无法使用C++语言及其标准库直接实现的部分用汇编实现,其它的计算仍然用C++语言写出。剩下的事情,跟其它几种方法的实现一样——让编译器看着办吧,它爱怎么优化就怎么优化。

---------------------
作者:晨星
来源:CSDN
原文:https://blog.csdn.net/steedhorse/article/details/4809493
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 将数据换为二进制比特意味着将给定的数据(如数字、文本或图像等)换为由 0 和 1 组成的比特序列,以便计算机可以对其进行处理和存储。 在二进制编码,每个数字或字符都被表示为一串比特(通常是 8 位,也称为一个字节),其每个比特可以是 0 或 1。例如,十进制数字 13 可以表示为二进制数 00001101,其左边的位数表示更高位的值,右边的位数表示更低位的值。 数据换为二进制比特通常是计算许多操作的基础,例如数据存储、通信和处理。 ### 回答2: 将数据换为二进制比特意味着将数据按照二进制编码方式进行表示和存储。二进制是一种使用0和1两个数字进行表示的计数系统,也是计算机内部数据的标准表示方式。 比特二进制的最小单位,代表一个二进制数的位,可以表示为0或1。在数据,我们将每个数据元素,如数字、字符或图像等,用二进制表示,并将其分割为一系列的比特。每个比特的值决定了对应数据元素在计算机内部的表示。 数据换为二进制比特的过程可以通过多种方法完成,比如利用计算机编程语言的函数、换工具或算法。例如,对于一个十进制数42,我们可以使用除以2取余法逐步获取其二进制表示。42除以2等于21余0,再将21除以2等于10余1,最后将10除以2等于5余0,5除以2等于2余1,2除以2等于1余0,1除以2等于0余1。将这些余数按相反的顺序排列起来,即可得到42的二进制表示为00101010。 数据换为二进制比特的过程对于计算机和数据存储来说是至关重要的,因为计算机硬件和软件系统通常使用二进制比特来表示、处理和存储数据。通过将数据换为二进制比特,我们可以更有效地传输、处理和计算大量的数据,并保证数据在计算机系统的准确性和一致性。 ### 回答3: 将数据换为二进制比特意味着将数据换为由0和1组成的二进制数字序列。比特二进制位的缩写,是计量计算机存储容量和传输速度的单位。二进制是一种基于2的计数系统,由两个数字0和1组成。在计算机系统,所有的数据和指令都被换为二进制比特形式进行处理。 数据换为二进制比特计算机来说是必要的,因为计算的所有运算和处理都是基于二进制的。例如,将整数换为二进制表示,可以更有效地存储和处理数据。在二进制表示,每一位代表一个2的幂次,可以通过基本的逻辑运算(如与、或和非)对其进行操作。 数据换为二进制比特的过程,通常通过编码算法实现。最常见的编码算法是ASCII码,它将字符映射为对应的二进制比特序列。其他编码算法还包括Unicode和UTF-8等。在编码过程,不同的字符或数据类型会被分配为不同长度的二进制比特序列,以满足不同数据表示的要求。 总之,数据换为二进制比特是将数据从原始形式换为由0和1组成的二进制数字序列的过程,这是计算机系统进行数据存储和处理的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值