【问题标题】:Algorithm for finding the smallest power of two that's greater or equal to a given value [duplicate]查找大于或等于给定值的两个的最小幂的算法[重复]
【发布时间】:2010-09-26 18:30:31
【问题描述】:

我需要找到大于或等于给定值的 2 的最小幂。到目前为止,我有这个:

int value = 3221; // 3221 is just an example, could be any number
int result = 1;

while (result < value) result <<= 1;

它工作正常,但感觉有点幼稚。有没有更好的算法来解决这个问题?

编辑。有一些很好的汇编程序建议,所以我将这些标签添加到问题中。

【问题讨论】:

标签: c++ algorithm assembly


【解决方案1】:

Bit Twiddling Hacks 页面上提供了对密切相关问题(即向下舍入而不是向上舍入)的可能解决方案的探索,其中许多解决方案比简单的方法要快得多,这是一个很好的资源您正在寻找的各种优化。最快的解决方案是使用具有 256 个条目的查找表,这将总操作计数从天真的方法的平均 62(通过类似的操作计数方法)减少到大约 7。使这些解决方案适应您的问题只是一个比较和增量的问题。

【讨论】:

【解决方案2】:

递归模板版本如何生成编译常量:

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundDown { enum{ value = Pow2RoundDown<(A | (A >> B)), B/2>::value }; };
template<uint32_t A>
struct Pow2RoundDown<A, 1> { enum{ value = (A | (A >> 1)) - ((A | (A >> 1)) >> 1) }; };

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundUp { enum{ value = Pow2RoundUp<((B == 16 ? (A-1) : A) | ((B == 16 ? (A-1) : A) >> B)), B/2>::value }; };
template<uint32_t A >
struct Pow2RoundUp<A, 1> { enum{ value = ((A | (A >> 1)) + 1) }; };

可以这样使用:

Pow2RoundDown<3221>::value, Pow2RoundUp<3221>::value

【讨论】:

    【解决方案3】:

    pow(2,ceil(log2(value));

    log2(value) = log(value) / log(2);

    【讨论】:

      【解决方案4】:

      这是位移技术的模板版本。

      template<typename T> T next_power2(T value)
      {
          --value;
          for(size_t i = 1; i < sizeof(T) * CHAR_BIT; i*=2)
              value |= value >> i;
          return value+1;
      }
      

      由于循环仅使用常量,因此编译器会将其展平。 (我检查过)该功能也是面向未来的。

      这是一个使用 __builtin_clz 的。 (也是未来的证明)

      template<typename T> T next_power2(T value)
      {
          return 1 << ((sizeof(T) * CHAR_BIT) - __builtin_clz(value-1));
      }
      

      【讨论】:

        【解决方案5】:

        任意对数函数可以通过除以2的对数转换为以2为底的对数:

        $ /usr/local/pypy-1.9/bin/pypy
        Python 2.7.2 (341e1e3821ff, Jun 07 2012, 15:38:48)
        [PyPy 1.9.0 with GCC 4.4.3] on linux2
        Type "help", "copyright", "credits" or "license" for more information.
        And now for something completely different: ``<arigato> yes but there is not
        much sense if I explain all about today's greatest idea if tomorrow it's
        completely outdated''
        >>>> import math
        >>>> print math.log(65535)/math.log(2)
        15.9999779861
        >>>> print math.log(65536)/math.log(2)
        16.0
        >>>>
        

        当然不会 100% 精确,因为涉及到浮点运算。

        【讨论】:

        • 你也可以print math.log(65535,2)
        【解决方案6】:

        我喜欢这种转变。

        我会接受的

            int bufferPow = 1;
            while ( bufferPow<bufferSize && bufferPow>0) bufferPow <<= 1;
        

        这样循环总是终止, && 之后的部分几乎不会被评估。 而且我不认为两行值得一个函数调用。您也可以根据自己的判断进行长或短,并且非常具有可读性。 (如果 bufferPow 变为负数,希望您的主代码能够快速退出。)

        通常您在算法开始时只计算一次 2 次方,因此无论如何优化都是愚蠢的。但是,如果有足够无聊的人会关心速度比赛......使用上面的例子和 255 256 257 .. 4195 4196 4197

        【讨论】:

          【解决方案7】:

          这很有效,而且速度非常快(在我的 2.66 GHz Intel Core 2 Duo 64 位处理器上)。

          #include <iostream>
          int main(void) {
              int testinput,counter;
              std::cin >> testinput;
              while (testinput > 1) {
                  testinput = testinput >> 1;
                  counter++;
              }
              int finalnum = testinput << counter+1;
              printf("Is %i\n",finalnum);
              return 0;
          }
          

          我在 3、6 和 65496 上进行了测试,给出了正确答案(4、8 和 65536)。

          对不起,如果这看起来有点神秘,我在写作之前受到了几个小时 Doom 的影响。 :)

          【讨论】:

          • 这看起来比 OP 开始的更糟糕,更不用说这里介绍的其他一些算法了
          【解决方案8】:

          我的版本:

          int pwr2Test(size_t x) {
              return (x & (x - 1))? 0 : 1; 
          }
          
          size_t pwr2Floor(size_t x) {
              // A lookup table for rounding up 4 bit numbers to
              // the nearest power of 2.
              static const unsigned char pwr2lut[] = {
                  0x00, 0x01, 0x02, 0x02,     //  0,  1,  2,  3
                  0x04, 0x04, 0x04, 0x04,     //  4,  5,  6,  7
                  0x08, 0x08, 0x08, 0x08,     //  8,  9, 10, 11
                  0x08, 0x08, 0x08, 0x08      // 12, 13, 14, 15
              };
          
              size_t pwr2 = 0;                // The return value
              unsigned int i = 0;             // The nybble interator
          
              for( i = 0; x != 0; ++i ) {     // Iterate through nybbles
                  pwr2 = pwr2lut[x & 0x0f];   // rounding up to powers of 2.
                  x >>= 4;                    // (i - 1) will contain the
              }                               // highest non-zero nybble index.
          
              i = i? (i - 1) : i;
              pwr2 <<= (i * 4);
              return pwr2; 
          }
          
          size_t pwr2Size(size_t x) {
              if( pwr2Test(x) ) { return x; }
              return pwr2Floor(x) * 2; 
           }
          

          【讨论】:

            【解决方案9】:

            本着 Quake II 的 0x5f3759df 和 Bit Twiddling Hacks 的 IEEE 版本的精神 - 这个解决方案达到了双精度以提取指数作为计算下限 (lg2(n)) 的方法。它比公认的解决方案快一点,并且比 Bit Twiddling IEEE 版本快得多,因为它避免了浮点数学。按照编码,它假定 double 是小端机器上的真正*8 IEEE 浮点数。

            int nextPow2(int n) 
            { 
                if ( n <= 1 ) return n;
                double d = n-1; 
                return 1 << ((((int*)&d)[1]>>20)-1022); 
            } 
            

            编辑:在同事的帮助下添加优化的 x86 程序集版本。速度提高了 4%,但仍比 bsr 版本慢了约 50%(在我的笔记本电脑上为 6 秒,n=1..2^31-2 为 4)。

            int nextPow2(int n) 
            { 
                if ( n <= 1 ) return n;
                double d;
                n--;
                __asm {
                  fild    n 
                  mov     eax,4
                  fstp    d 
                  mov     ecx, dword ptr d[eax]
                  sar     ecx,14h 
                  rol     eax,cl 
              }
            } 
            

            【讨论】:

            • 与 BSR 指令相比,这真的有效吗?
            【解决方案10】:

            我知道这是投反对票,但如果数字足够小(如 8 位或 16 位),直接查找可能会最快。

            // fill in the table
            unsigned short tab[65536];
            unsigned short bit = tab[i];
            

            可以通过先执行高位字然后执行低位字来将其扩展到 32 位。

            //
            unsigned long bitHigh = ((unsigned long)tab[(unsigned short)(i >> 16)]) << 16;
            unsigned long bitLow = 0;
            if (bitHigh == 0){
                bitLow = tab[(unsigned short)(i & 0xffff)];
            }
            unsigned long answer = bitHigh | bitLow;
            

            shift-or 方法可能并不好,但也许可以扩展到更大的字数。

            (实际上,这给出了最高的 1 位。您必须将其左移 1 才能获得下一个更高的 2 次方。)

            【讨论】:

            • 使用 256 个条目的查找表并将结果用于 4 字节字的所有 4 字节是非常可行的。使用 65536 条目表的内存/速度折衷不是很好(14% 加速,25500% 内存增加),
            • @Sparr。你说得对。转移或方法很难击败,但留意更多剥猫皮的方法很有趣。
            • 有一次我听了 Dijkstra 关于有趣算法的讲座。他有一个学生通过执行一系列 3 位反转来进行 n 位旋转。
            【解决方案11】:

            您并没有真正说出“更好的算法”是什么意思,但由于您介绍的内容非常清楚(如果有些缺陷),我假设您追求的是一种更有效的算法。

            Larry Gritz 给出了可能是最有效的 c/c++ 算法,没有查找表的开销,并且在大多数情况下就足够了(有关类似算法,请参阅 http://www.hackersdelight.org)。

            正如其他地方所提到的,如今大多数 CPU 都有机器指令来计算前导零的数量(或等效地返回 ms 设置位),但是它们的使用是不可移植的,并且 - 在大多数情况下 - 不值得努力。

            然而,大多数编译器都有“内在”功能,允许使用机器指令,但以更可移植的方式。

            Microsoft C++ 有 _BitScanReverse() 并且 gcc 提供 __builtin_clz() 来高效地完成大部分工作。

            【讨论】:

              【解决方案12】:

              这是我最喜欢的。除了初始检查它是​​否无效(=0 的数字,你可以跳过它),它没有循环或条件,因此将优于大多数其他方法。这与埃里克森的回答类似,但我认为我在开头递减 x 并在结尾添加 1 比他的回答要少一些尴尬(并且还避免了结尾的条件)。

              /// Round up to next higher power of 2 (return x if it's already a power
              /// of 2).
              inline int
              pow2roundup (int x)
              {
                  if (x < 0)
                      return 0;
                  --x;
                  x |= x >> 1;
                  x |= x >> 2;
                  x |= x >> 4;
                  x |= x >> 8;
                  x |= x >> 16;
                  return x+1;
              }
              

              【讨论】:

              • 除了细微的语法差异和额外的初始检查外,您的版本也几乎与 Henry S. Warren, Jr. 的“Hacker's Delight”中给出的版本相同。
              • @Boojum:感谢您提及那本书。我已经检查过了,它有我需要的解决方案,还有更多!
              • “if (x
              • @Boyan:此解决方案不可移植,例如,它如何在 x64 上工作? (它没有)
              • 除了@Boojum 提到的 Hacker's Delight 之外,这个解决方案也几乎逐字出现在 Bit Twiddling Hacks (2001;graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) 中,它归功于 Sean Anderson,甚至更早在1997 年由 Pete Hart 和 William Lewis 撰写的 Usenet 线程 (groups.google.com/forum/#!topic/comp.lang.python/xNOq4N-RffU)。
              【解决方案13】:

              下面的代码反复去除最低位,直到数字是 2 的幂,然后将结果加倍,除非数字开始是 2 的幂。它的优点是运行时间与设置的位数成正比。不幸的是,它的缺点是在几乎所有情况下都需要比问题中的代码或汇编建议更多的指令。我只是为了完整性才包含它。

              int nextPow(int x) {
                int y = x
                while (x &= (x^(~x+1))) 
                  y = x << 1;
                return y
              }
              

              【讨论】:

                【解决方案14】:
                ceil(log2(value))
                

                ilog2() 可以用 3 个 asm 指令计算,例如 http://www.asterisk.org/doxygen/1.4/log2comp_8h-source.html

                【讨论】:

                • 这并没有慢的特性,而是在日志表查找都是预先计算的情况下,取 log2(value) 的结果并将其四舍五入到最接近的整数在效率上不会被击败
                • Log-base-2 通常将浮点数作为参数。您是说您有一个查找表,其中包含每个可能的浮点数的条目?我希望不会...当然,最快的方法是使用 2^32 个条目的查找表,但这有点占用内存。
                • @J.F.:如果我们加入一些汇编程序,您的解决方案看起来确实更好。感谢您的链接!
                • 你的意思是1
                • 链接已损坏...
                【解决方案15】:

                在 Intel 硬件上,BSR 指令与您想要的很接近 - 它会找到最高有效位。如果您需要更精确,您可以想知道剩余位是否精确为零。 我倾向于假设其他 CPU 会有类似 BSR 的东西——这是一个你想要回答的问题以标准化一个数字。 如果您的数字超过 32 位,那么您将从最重要的 DWORD 进行扫描,以找到第一个设置了 ANY 位的 DWORD。 Edsger Dijkstra 可能会说,上述“算法”假设您的计算机使用二进制数字,而从他那种崇高的“算法”角度来看,您应该考虑图灵机或其他东西——显然我的风格更务实。

                【讨论】:

                • 是的,也许我会做一些汇编程序。在找到最重要的设置位之后,我想我可以做: if (~most_sign_bit & value) 来查找我是否必须将值左移一次。对吗?
                • 我在 MSDN 中查看了一个名为 _BitScanReverse() 的编译器内部 - 这比汇编器更好,因为您不能在 x64 中进行内联汇编,并且您不想浪费一个过程调用一个外部 x64 例程。当然,假设您使用的是 MS 编译器。
                • (~MSB & value ) 听起来很完美 - 当然单步就可以确定!
                • 有一些清理工作要做,因为 4 和 5 都会为 MSB 返回 2,而这些值的正确答案分别是 4 和 8。不过,我喜欢 BSR 解决方案——我倾向于忘记那个指令。
                • @DocMax:是的,这就是为什么我会在 BSR 之后使用 (~MSB & value) 来确定我是否需要左移一次。
                【解决方案16】:

                您的实现并不幼稚,它实际上是合乎逻辑的,只是它是错误的 - 对于大于最大整数大小 1/2 的数字,它返回负数。

                假设您可以将数字限制在 0 到 2^30 的范围内(对于 32 位整数),它会正常工作,并且比任何涉及对数的数学函数都要快得多。

                无符号整数会更好,但最终会出现无限循环(对于大于 2^31 的数字),因为使用

                【讨论】:

                • 是的,这些值会大于零且远小于 2^31。
                • 那么你所拥有的就是尽可能快地获得。我不怀疑有一个布尔代数解决方案可以在 2 或 3 次操作中完成,但你会牺牲很多可读性以获得非常小的性能提升。
                • 我认为您可以使用二进制搜索来加快速度:使用(静态)计算的数字范围中间值初始化结果并左/右移动。您在此搜索树上预先计算的步骤越多,平均移位量就越低
                • 我只是不知道这是否真的会更快,因为对于许多范围,尽管有更多的班次,但那里的解决方案可能会更快。
                猜你喜欢
                • 1970-01-01
                • 2016-09-30
                • 2020-12-23
                • 2021-02-24
                • 1970-01-01
                • 1970-01-01
                • 2015-05-25
                • 2021-10-24
                相关资源
                最近更新 更多