【朝花夕拾】二进制操作的骚操作 --- 计算二进制数中1的个数

在有的场景中我们需要计算二进制位数的个数,比如LitCode中的一些二进制题目中。那么我们就来看下如何求取二进制数中的1的个数。

第一反应就是遍历数值中的二进制位,判断相应位数是否为1,若为1则累加。以下代码使用到与运算的特性:

由二元运算规则
1 & A = A
0 & A = 0
得:
假定 数值序列 A = {a1,a2,...,aN,..,a32} 其中 a 取值为 0 或 1
数值序列 B = {0,0,...,1,...,0},其中 aN = 1, 其他为 0
A & B = C {a1 & 0, a2 & 0, ..., aN & 1, ...,a32 & 0}
	  = {0, 0, ..., aN, ..., 0}

因此得代码如下

int bit_count(unsigned int num) {
    int result = 0;
    for (size_t i = 0; i < 32; i++)
    {
        if ( num & (1 << i )) {
            result++;
        }
    }
    return result;
}

仔细思考这个代码,总觉这个并非最优的,输入数字的位数为32位,我们必须遍历32次才能得到结果,应该不是最优的。因此想到了方法二

方法:A & (A - 1) 可以去掉最后一位的1
不断去掉最后1位1,直到变为0,去掉的1的个数就是我们要求的值

用最蠢笨的方法证明一下上述方法是否可以去掉最后一位1:
我们都知道计算机进行计算都是用补码运算,正数的补码是其本身,负数的补码为其按位取反之后+1
A - 1 ==> A + (-1)
-1 的补码(以32位补码运算) ==> 11111111111111111111111111111111
设 A = {a1,a2,a3,a4,...a32} an 为最后一个1 则 a n+1 -- a32 都为0
1 + 0 = 1
1 + 1 = 10

遇到最后一个an 则必然会产生进位 1,an 则变成 0, 计算变成如下算式
a0a1...an-1 + 1 + 111111...
根据加法结合律:
a0a1...an-1 + 1 + 111111... = a0a1...an-1 + (1 + 111111...)
= a0a1...an-1 + 0
= a0a1...an-1 0
再加上后面的结果 1111
即 a0a1...an-1 0 111...

因此:
A & (A - 1) =
	{a0 a1 ... an-1 0 111...}
  & {a0 a1 ... an-1 1 000...}
=   {a0 a1 ... an-1 0 000...}

代码如下:

int bit_count(unsigned int num) {
    int result = 0;
    while (num != 0)
    {
        num &= (num - 1);
        result++;
    }
    return result;
}

现在是否就是最优解了呢?并不是,下面这个方法是Java中Integer.bitCount的实现,除了膜拜实在不知道如何感慨。

public static int bitCount(int i) {
     // HD, Figure 5-2
     i = i - ((i >>> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
     i = (i + (i >>> 4)) & 0x0f0f0f0f;
     i = i + (i >>> 8);
     i = i + (i >>> 16);
     return i & 0x3f;
}

不由让人想到之前看到的一片博客中看到的一段求解开平方倒数的代码,贴上供大家观摩观摩,注意注释中的惊喜

float Q_rsqrt( float number )
{
   long i;
   float x2, y;
   const float threehalfs = 1.5F;

   x2 = number * 0.5F;
   y = number;
   i = * ( long * ) &y; // evil floating point bit level hacking
   i = 0x5f3759df - ( i >> 1 ); // what the fuck?
   y = * ( float * ) &i;
   y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
   // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

   #ifndef Q3_VM
   #ifdef __linux__
     assert( !isnan(y) ); // bk010122 - FPE?
   #endif
   #endif
   return y;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值