【问题标题】:Fastest way to find binary base [duplicate]找到二进制基数的最快方法[重复]
【发布时间】:2013-05-25 11:46:33
【问题描述】:

我正在尝试查找数字的二进制基数,例如将数字四舍五入到它下面的最大整数的 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;
}

谢谢:)

【问题讨论】:

  • 关闭但不完全是 dup:stackoverflow.com/questions/671815/…
  • 您可能想查看 Andrei Alexandrescu 的 Three Optimization Tips for C++,他在其中基本上使用了这个问题作为示例。幻灯片:24,视频:~30:00。
  • 看看这个无分支代码以找到ceil(log2(x)):stackoverflow.com/a/15327567/1553090——也许你可以适应它。
  • 不完全是一个重复,尽管该解决方案效果很好。 @bugsbunny:如果您想要一种简单易懂的方法,您可以尝试“向上​​取整到 2 的下一个最高幂”算法 here 并右移 1 位

标签: c algorithm bit-manipulation


【解决方案1】:

我知道递归通常不如迭代高效,但我忍不住——我喜欢好的递归:

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

ADDENDUM:实际时间显示,在经过优化编译时,这比 @LaurenceGonsalves 的迭代版本略快。

【讨论】:

    【解决方案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;
    }
    

    我针对topBitbuiltIn 方法对该算法进行了一些基准测试。具有 10M 次迭代的循环生成一个“长”随机数,在我的系统上需要 362 毫秒(没有编译器优化)。如果循环包括其中一种计算方法,则时间增加如下:

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

    内置的方法绝对是最快的,而且明显领先 - 这并不令人惊讶!对于 64 位数字,topBit 平均需要 32 个循环(设置了一半的位,所以跳过),二进制只需要 5 个循环,因此您预计速度差异约为 6 倍;这大致就是您所看到的。当我将ulli定义为unsigned short(16位)时,时差约为2x。

    【讨论】:

    • msb = sizeof(num) >> 1; ?? msb(3524) != sizeof(3524) >> 1 ; sizeof(3524) == sizeof(2)(在 32 位平台上 == 4)
    • @ahmedmasud 我选择的变量名很差。 temp 最终将成为您要查找的号码。 msb 以 16 开头(如果 int 是 32 位),所以 temp=1&lt;&lt;16 作为第一个猜测等。我编辑了答案以澄清;感谢您指出!
    • 此解决方案在 num 设置了任何高位时存在问题。 int topBit(int n)、Paulpro等解决方案更快更全。顺便说一句,这是一个有趣的练习。
    • 任何体面的编译器都会将除以 2 变为移位。为了清楚起见,最好进行编码,尤其是当晦涩的编码让您一无所获时。
    • @Gene - 我不认为&gt;&gt;=1 是“晦涩的”,但我同意你关于“清晰度胜过速度”的观点并修改了我的例子。同时利用这个机会修复了明显的错误(temp&gt;&gt;msb应该是temp&gt;&gt;=msb)。
    【解决方案3】:

    又短又甜...
    (提问者的示例代码在值 >= 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;
      }
    }
    

    【讨论】:

    • 这看起来很像问题中的“这是我尝试过的”示例?...
    • @Floris。同意,它很像这个例子。它需要 2 处更正:在while() 之后执行&gt;&gt;= 之后 并以正确的unsigned long long 开头。因此,从算法上讲,这并没有增加讨论,但是发布的许多答案在算法上都很有趣,但在某些值上存在问题。 SO中的许多问题都已关闭,因为它们通常仅涉及算法。我解决了具体的程序弱点并提出了建议。顺便说一句,你的时间分析很棒!测试是否也应该包括针对ULLONG_MAX 的正确性?
    【解决方案4】:

    This classic document 有很多方法可以找到整数的底数(对数基数 2)。找到日志后,你想要的数字当然是 1

    最吸引人的建议是这个

    // 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;
    

    上面的代码通过将整数存储在尾数中同时将指数设置为 252 来加载一个 64 位(IEEE-754 浮点)双精度数和一个 32 位整数(没有填充位)。从这个新minted double, 252(表示为双精度数)被减去,它将结果指数设置为输入值 v 的以 2 为底的对数。剩下的就是将指数位移动到适当的位置(向右 20 位)并减去偏差, 0x3FF(十进制为 1023)。这种技术只需要 5 次操作,但许多 CPU 处理双精度数的速度很慢,而且必须适应架构的字节序。

    所以你想要的最终结果将是1 &lt;&lt; r。请注意,现在对双精度数的操作比撰写本文时快很多。这段代码最好的一点是它不包含任何分支,所以流水线会很好。你绝对应该试一试。我现在没有时间尝试基准测试,但它会很有趣。

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

    【讨论】:

    • 可能不是标准 C,但很酷!
    • 对于标准 C,只需使用 int r; frexp(v, &amp;r); r -= 1;
    【解决方案5】:

    如果您使用 GCC 进行编译,您可以使用 GCC builtins 执行此操作。内置的__builtin_clzll 计算无符号长整数中前导零的数量。你可以用它来计算最高位的位置,然后左移 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);
    

    【讨论】:

    • 我想你想要1LLU &lt;&lt; ... 而不是1
    • @chux 谢谢!我更新了。
    【解决方案6】:

    建议的位旋转技巧的最坏情况性能可以/应该通过逐渐or将几个位变为一来改善:

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

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

    【讨论】:

    • @bugsbunny 示例代码使用无符号整数,此方​​法转换为无符号整数后是否适用于 MAX 无符号整数? (我担心的是 n+1 溢出。)
    • 不,它没有。至少 while 循环适用于 MAX int,因此人们可能会找到一个更好的返回值公式。 (n>>1)+1 可以解决这个问题,但不记得 func(0) 的规范
    【解决方案7】:
    int test(int num) {
        int res = (num==0?0:1);
        while(num>1) {
            num=num>>1;
            res=res<<1;
        }
        return res;
    }
    

    num 不大时比下面快。

    【讨论】:

    • @bugsbunny 没有指定如何处理 f(0)。请注意 test(0)test(1) 都返回 1。
    • @chux 已修复,但我不会考虑是否 num
    【解决方案8】:

    这比你的版本快大约 2 倍:

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

    【讨论】:

    • 如果 num > 0x8000000000000000LLU,while 循环不会终止。 r 达到值 0x8000000000000000LLU 将左移到 0,然后循环继续进行。
    【解决方案9】:
    int topBit(int n) {
        while (true){
            m = n & (n-1);
            if (m == 0) return n;
            n = m;
        } 
    }
    

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

    【讨论】:

    • 这很好地补充了@markransom 的答案。位与位与位或!要么工作。凉爽的。小整数可能更快;我想知道什么大小的整数二进制搜索获胜......
    • 我也喜欢,比我的更短更清晰。
    【解决方案10】:

    这是一个与寻找最高位问题密切相关的问题;因为在那之后它只是有点移位:

    这里很好地描述了寻找 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; 
    

    你有它。

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

    或者

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

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

    【讨论】:

    • 我必须为这个很棒的hackersdelight链接投票!
    【解决方案11】:

    我假设如果一个数字已经是 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 将设置最底部的零位。

    【讨论】:

    • 我不得不考虑一下,但现在我喜欢它了。它比二进制搜索快吗?我认为它只保存非零位的迭代;我说的对吗?
    • @Floris,对 - 它只跳过非零位。最好的情况是没有零位,最坏的情况是设置了 2 位而其余的位为零。二进制搜索可能会更好。
    • @bugsbunny 示例代码使用无符号整数。我也问过另一个解决方案,这种方法转换为无符号,是否与 MAX 无符号一起使用? (我担心的是 n+1 溢出 - 看起来它会立即退出 while 循环)也许对 MAX UINT 的简单测试可以解决问题。
    猜你喜欢
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-23
    相关资源
    最近更新 更多