找到二进制基数的最快方法

时间:2013-05-30 02:35:51

标签: c algorithm bit-manipulation

我试图找到一个数字的二进制基数,比如将数字舍入到它下面的最大整数的floor函数,我想将数字四舍五入到它下面的第一个二进制数。

例如:

for 1000 it should be 512
for 10 it should be 8
for 208 it should be 128

这是我尝试过的。我觉得日志功能会占用更多资源所以有没有更快的方法呢?

#include<stdio.h>
int main()  {
    unsigned long long int num;
    unsigned long long int mask;
    scanf("%llu", &num);
    mask = 0x80000000;
    while(mask >>= 1)   {
        if (mask & num)
            break;
    }
    printf("%llu\n", mask);
    return 0;
}

感谢:)

11 个答案:

答案 0 :(得分:5)

int topBit(int n) {
    while (true){
        m = n & (n-1);
        if (m == 0) return n;
        n = m;
    } 
}

n & (n-1)清除最底部的位置。只要这样做,直到你达到零,然后你知道前一个值只有一个位设置(输入中设置的最高值)。

答案 1 :(得分:2)

以二进制表示数字,然后查找最高有效位(最高非零位)。天真地你可以通过一次右移一位直到它为零来做到这一点 - 这是“一个太多”。这基本上就是你尝试过的方法。更快一点的是二分搜索。对于32位整数,向右移16;如果仍然> 0,右移8等。我相信你可以从这里弄明白。

代码示例:

typedef unsigned long long int ulli;
ulli floor2(ulli num){
  int msb = 8*sizeof(num)/2;
  ulli temp = ((ulli)1)<<msb;
  while(msb>1){
    msb/=2; // using divide for clarity
    if(temp>num) temp>>=msb; else temp<<=msb;
  }
  if (temp>num) temp/=2;
  return temp;
}

我针对topBit以及builtIn方法运行了此算法的一些基准测试。一个10M迭代的循环,生成一个“长”随机数,在我的系统上需要362 ms(没有编译器优化)。如果循环包含其中一种计算方法,则时间增加如下:

=============  total    net
builtin:         407     45
binary search:   579    215
topBit:         2295   1933

内置方法绝对是最快的 - 不是很令人惊讶!对于64位数字,topBit平均需要32个循环(设置一半的位,所以跳过)和二进制只有5个循环,所以你可以期待大约6倍的速度差异;这大致就是你所看到的。当我将ulli定义为unsigned short(16位)时,时差约为2倍。

答案 2 :(得分:2)

如果您正在使用GCC进行编译,则可以使用GCC builtins执行此操作。内置__builtin_clzll计算无符号长long中前导零的数量。你可以用它来计算最重要位的位置,然后多次左移1来得到答案:

#include <limits.h>

然后使用:

unsigned long long result = 
  num ? 1LLU << (sizeof(unsigned long long)*CHAR_BIT - __builtin_clzll(num) - 1) : 0;

printf("%llu\n", result);

答案 3 :(得分:2)

This classic document有很多方法可以找到整数的下限(日志基数2)。找到日志后,您想要的数字当然是1&lt;&lt;日志中。

最引人入胜的建议就是这个

// Find the integer log base 2 of an integer with an 64-bit IEEE float 
int v; // 32-bit integer to find the log base 2 of
int r; // result of log_2(v) goes here
union { unsigned int u[2]; double d; } t; // temp

t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = v;
t.d -= 4503599627370496.0;
r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
  

上面的代码通过在尾数中存储整数来加载64位(IEEE-754浮点)double和32位整数(没有paddding位),而指数设置为252。减去minted double,252(表示为double),将得到的指数设置为输入值的对数基数v,剩下的就是将指数位移位到位(右边20位)并减去偏差,0x3FF(十进制1023)。这种技术只需要5次操作,但是许多CPU在操作双精度方面都很慢,并且必须适应架构的结束性。

所以你想要的最终结果是1 << r。请注意,与撰写本文时相比,双打操作现在更快 。关于这段代码的最好的事情是它不包含分支,因此管道很好。你一定要试一试。我刚才没有时间尝试基准测试,但这很有意思。

我不能保证此代码符合C标准。

答案 4 :(得分:1)

我假设如果一个数字已经是2或0的幂,则应该不加改变地返回它。仅限正数。

int floor2(int n)
{
    if ((n & (n-1)) == 0)
        return n;
    while (((n+1) & n) != 0)
    {
        n = n | (n+1);
    }
    return (n + 1) >> 1;
}

这里花哨的位数利用了这样一个事实,即从一个数字中减去1(即2的幂)将所有位设置在它下面,而在数字上加1将设置最底部的零位。

答案 5 :(得分:1)

这个问题与发现最重要的问题密切相关;因为在那之后它只是一点点转移:

在这里详细描述了MSB:

Find most significant bit (left-most) that is set in a bit array

然后你做这样的事情:

int foo = 1000;
int bar = ((msb(foo) << 1) - 1) >> 1; 
if ( bar > foo ) bar = bar >> 1; 

你有它。

如果您使用的是英特尔架构,可以在gcc中使用__builtin_clz(计算前导零)来获取msb;

这是一种非常有趣的方法来计算没有CPU支持的前导零

http://www.hackersdelight.org/hdcodetxt/nlz.c.txt

答案 6 :(得分:1)

简短而甜蜜...... (提问者的示例代码在值> = 0x80000000LLU时遇到问题,修复此处。)
这只需要在循环中进行1次比较,而不是2次。

unsigned long long int MSMask(unsigned long long int) {
  if (num == 0) {
    return 0;
    }
  else {
    unsigned long long int mask = 0x8000000000000000LLU;
    while (!(mask & num)) mask >>= 1;
    return mask;
  }
}

答案 7 :(得分:0)

这比您的版本快2倍:

uint64_t g(uint64_t num) {
    uint64_t r = 1;
    num = num >> 1;
    while(num>r)   {
        r <<= 1;
    }
    return r;
}

答案 8 :(得分:0)

int test(int num) {
    int res = (num==0?0:1);
    while(num>1) {
        num=num>>1;
        res=res<<1;
    }
    return res;
}

在num不大时比下面快。

答案 9 :(得分:0)

建议的比特混乱技巧的最坏情况表现可以/应该通过逐渐or几个比特来改善:

 int s=1;
 while ((n+1) & n) {
    n|=n >> s;
    s<<=1;
 }
 return (n+1) >> 1;

当所有最低有效位都为1时,此片段退出,只需要一些log2(log2(n))迭代。

答案 10 :(得分:0)

我知道递归通常不如迭代效率高,但我无法自拔 - 我喜欢一个很好的递归:

unsigned topBit(unsigned n) {
   unsigned m = n & (n-1);
   return m ? topBit(m) : n;
}

ADDENDUM :在使用优化进行编译时,实际时间显示比@ LaurenceGonsalves的迭代版本略快。