比特计算 - 整型中1的个数计算

本文介绍了计算64位整数中1的个数的一种快速算法,通过利用预设的统计表进行分段查询来减少计算量。此外,还提供了一种基于位操作的简洁算法,并探讨了其与传统方法的性能对比。最后,通过简单的数学变换,提出了一个针对特定场景优化的算法实例。

今天看到国外国际象棋程序beobulf中一段计算64位长整数中1 bit的个数的计算法。它使用inbits表代表8位整数的1的个数的统计表,然后将64位分8段分别查表累计,程序如下,得到了一个快速算法。理论上这个算法够快了,除非你做一张更大的统计表。

/* A list of the number of bits in numbers from 0-255.  This is used in the  * bit counting algorithm.  Thanks to Dann Corbit for this one. */
static int inbits[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,
};

/* This algorithm thanks to Dann Corbit.  Apparently it's faster than
 * the standard one.  */
int Count(const BITBOARD B) {
  return inbits[(unsigned char) B] +
    inbits[(unsigned char) (B >> 8)] +
    inbits[(unsigned char) (B >> 16)] +
    inbits[(unsigned char) (B >> 24)] +
    inbits[(unsigned char) (B >> 32)] +
    inbits[(unsigned char) (B >> 40)] +
    inbits[(unsigned char) (B >> 48)] +
    inbits[(unsigned char) (B >> 56)];

}
  当我想有没有更简洁的方法时,这让我想到过去一个面试题,要用一行代码检查一个数是否是2的幂。当时没做出来,之后3天才想到判据是:  0 == ( x^(x-1) ),当然于事无补。

  这时感觉这个判据是可以用在整形中1的个数的计算中的,当走过2条街后就想到了。当然这个算法在平均时间上不如上面算法,最大要循环64次(x=2^64-1时)才能获得结果。而且循环中还有跳转指令耗时,不过和逐个比特检查相比效率要高,还有优点就是简洁。

int bitcount( u64 x )

{
   int bitcnt;
   for( bitcnt = 0 ; x ; bitcnt++ )
   {
       x = x ^ (x-1);
   }
   return bitcnt;
}

此即 『Hacker's Delight』中 “Figure 5-3 Counting 1-bits in a sparsely populated word.”


可能存在的一个改进算法,能做的就是减少可能的跳转循环过程,这也是beobulf累计时没用循环的原因,你懂的,ok。

int bitcount2( u64 x )
{
    int bitcnt;

    for( bitcnt = 0 ; x ; )
    {
        x = x ^ (x-1);       bitcnt += !!x;
        x = x ^ (x-1);       bitcnt += !!x;
        x = x ^ (x-1);       bitcnt += !!x;
        x = x ^ (x-1);       bitcnt += !!x;
    }
    return bitcnt;
}


附(2012-6-18)

最近看hacker's delight, 5-1开篇提到的算法,对于已知字长的“1”数目计算方法相当好。对于32位整数的计算方法如下。

x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x >>16) & 0x0000FFFF);
以上算法不用查询表,也可以在log2(32)=5次计算后得到结果,除赋值外一共需要20个逻辑运算。考虑到其中某些步骤不存在进位影响计算结果的危险,进一步优化后的算法只需要 15个逻辑运算。

int populate32(unsigned x) {
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}
64位版本可以是

int populate64(unsigned x) {
    x = x - ((x >> 1) & 0x5555555555555555);
    x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x + (x >> 32);
    return x & 0x0000007F;
}




                
### C语言实现计算整数二进制表示中1的数量 #### 方法一:逐位检查法 通过循环遍历每一位来检测是否为`1`,并累加计数器。 ```c #include <stdio.h> int count_one_bits(int n){ int count = 0; for (int i = 0; i < 32; ++i) { if ((n >> i) & 1 == 1) count++; } return count; } int main(){ int num; printf("请输入一个数:"); scanf("%d", &num); printf("二进制中1个数:%d\n", count_one_bits(num)); return 0; } ``` 这种方法适用于固定长度的数据类型,比如32位整型变量[^2]。 #### 方法二:移除最低位的'1' 利用按位运算特性,每次将最右边的一个`1`变为`0`直到整个数值变成零为止。此方式效率更高因为不需要处理所有的比特位而是仅针对存在的`1`s操作。 ```c #include <stdio.h> int numberOfOnes(int n){ int cnt = 0; while (n != 0){ n &= (n - 1); // 将当前最小的‘1’置为‘0’ cnt++; } return cnt; } int main(){ int number; printf("输入要测试的数字:"); scanf("%d",&number); printf("该数字含有%d个'1'\n",numberOfOnes(number)); return 0; } ``` 这种技术特别适合用来快速判断给定正整数是否属于2的幂次方形式——如果某整数恰好有且仅有一次出现过`1`则其必然是某个2的指数项[^3]. #### 方法三:查表法(预定义数组) 预先构建好一张表格保存从0到最大可能值之间所有整数对应的汉明重量(即其中含有的`1`的数量),查询时只需直接索引即可得到结果。不过这通常只用于特定场景下的优化,在通用编程环境中不太实用由于占用额外空间资源较多。 对于上述提到的各种方案而言,选择哪一种取决于具体应用场景的要求以及性能考量因素。当面对较大规模数据集或是追求极致速度的情况下可以优先考虑第二种方法;而对于小型项目或者学习练习来说第一种做法已经足够满足需求了[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值