【问题标题】:Previous power of 22的前次幂
【发布时间】:2010-04-21 01:50:50
【问题描述】:

有很多关于如何找到给定值的 2 的下一个幂的信息(请参阅参考资料),但我找不到任何获得前一个 2 的幂的信息。

到目前为止,我发现的唯一方法是保留一个包含 2 的所有幂的表,最高可达 2^64,然后进行简单的查找。

【问题讨论】:

  • 得到 2 的下一个幂,然后除以 2...?
  • 得到下一个,除以2。
  • 这应该是对您链接的现有算法的非常简单的改编。您可以发布您所拥有的内容,我们可以为您提供提示吗?
  • 他在求两个最接近给定值(小于,不大于给定值)的幂
  • 清除除最高位之外的所有位。这是一个通用配方,您可以通过多种方式实现

标签: algorithm


【解决方案1】:

来自Hacker's Delight,一个不错的无分支解决方案:

uint32_t flp2 (uint32_t x)
{
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >> 16);
    return x - (x >> 1);
}

这通常需要 12 条指令。如果您的 CPU 有“计数前导零”指令,您可以用更少的时间完成。

【讨论】:

  • FWIW,这比 AMD CPU 的 bsr 解决方案快得多,32 位版本快 3-4 倍,64 位版本快 1.5-2 倍。我听说英特尔的情况正好相反,但我无法使用他们的 CPU 进行测试。
【解决方案2】:
uint32_t previous_power_of_two( uint32_t x ) {
    if (x == 0) {
        return 0;
    }
    // x--; Uncomment this, if you want a strictly less than 'x' result.
    x |= (x >> 1);
    x |= (x >> 2);
    x |= (x >> 4);
    x |= (x >> 8);
    x |= (x >> 16);
    return x - (x >> 1);
}

感谢您的回复。我将尝试对它们进行总结并更清楚地解释。 该算法所做的是将第一个“一”位之后的所有位都变为“一”,因为这些是唯一可以使我们的“x”大于其先前的 2 次方的位。 在确保它们是“一”之后,它只是将它们删除,而第一个“一”则完好无损。取而代之的是我们之前的 2 次幂。

【讨论】:

    【解决方案3】:

    可能是最简单的方法(对于正数):

    // find next (must be greater) power, and go one back
    p = 1; while (p <= n) p <<= 1; p >>= 1;
    

    如果您想优化,可以通过多种方式进行更改。

    【讨论】:

      【解决方案4】:

      这是一个供后代使用的衬里(红宝石):

      2**Math.log(input, 2).floor(0)
      

      【讨论】:

      • 他把问题列在“算法”下面,所以我给出了一个面向算法的解决方案。
      • 这正是我在谷歌搜索“2 的最近幂”并找到这个 StackOverflow 问题时希望找到的信息。
      【解决方案5】:

      g++ 编译器提供了一个内置函数 __builtin_clz,它计算前导零:

      所以我们可以这样做:

      int previousPowerOfTwo(unsigned int x) {
        return 1 << (sizeof(x)*8 - 1) - __builtin_clz(x);
      }
      
      int main () {
        std::cout << previousPowerOfTwo(7)  << std::endl;
        std::cout << previousPowerOfTwo(31) << std::endl;
        std::cout << previousPowerOfTwo(33) << std::endl;
        std::cout << previousPowerOfTwo(8)  << std::endl;
        std::cout << previousPowerOfTwo(91) << std::endl;
      
        return 0;
      }
      

      结果:

      4
      16
      32
      8
      64
      

      但请注意,对于x == 0__builtin_clz 返回未定义。

      【讨论】:

        【解决方案6】:

        如果你能得到 2 的下一个更高的幂,那么 2 的下一个更低的幂要么是下一个更高的幂,要么是下一个更高的幂。这取决于您认为 2 的任何幂的“下一个更高的”(以及您认为是 2 的下一个更低的幂)。

        【讨论】:

          【解决方案7】:

          怎么样

          if (tt = v >> 16)
          {
             r = (t = tt >> 8) ? 0x1000000 * Table256[t] : 0x10000 * Table256[tt];
          }
          else 
          {
            r = (t = v >> 8) ? 0x100 * Table256[t] : Table256[v];
          }
          

          它只是http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup的修改方法。 这需要大约 7 次操作,并且用移位替换乘法可能会更快。

          【讨论】:

            【解决方案8】:

            仅使用位操作的解决方案:

            long FindLargestPowerOf2LowerThanN(long n)
            {
                Assert.IsTrue(n > 0);
            
                byte digits = 0;
                while (n > 0)
                {
                    n >>= 1;
                    digits++;
                }                            
            
                return 1 << (digits - 1);
            }
            

            例子:

            FindLargestPowerOf2LowerThanN(6):
            Our Goal is to get 4 or 100
            1) 6 is 110
            2) 110 has 3 digits
            3) Since we need to find the largest power of 2 lower than n we subtract 1 from digits
            4) 1 << 2 is equal to 100 
            
            FindLargestPowerOf2LowerThanN(132):
            Our Goal is to get 128 or 10000000
            1) 6 is 10000100
            2) 10000100 has 8 digits
            3) Since we need to find the largest power of 2 lower than n we subtract 1 from digits
            4) 1 << 7 is equal to 10000000 
            

            【讨论】:

              【解决方案9】:

              我在这里写下我的答案,以备将来需要参考。

              对于 C 语言,这是我认为之前 2 函数的“终极”解决方案。以下代码:

              • 针对 C 语言(不是 C++),

              • 如果编译器支持,则使用编译器内置函数生成高效代码 (CLZ or BSR instruction),

              • 是可移植的(标准 C 和无汇编),内置函数除外,并且

              • 解决编译器内置函数的未定义行为(当 x 为 0 时)。

              如果您使用 C++ 编写,您可以适当地调整代码。请注意,C++20 引入了 std::bit_floor,它的作用完全相同。

              #include <limits.h>
              
              #ifdef _MSC_VER
              # if _MSC_VER >= 1400
              /* _BitScanReverse is introduced in Visual C++ 2005 and requires
                 <intrin.h> (also introduced in Visual C++ 2005). */
              #include <intrin.h>
              #pragma intrinsic(_BitScanReverse)
              #pragma intrinsic(_BitScanReverse64)
              #  define HAVE_BITSCANREVERSE 1
              # endif
              #endif
              
              /* Macro indicating that the compiler supports __builtin_clz().
                 The name HAVE_BUILTIN_CLZ seems to be the most common, but in some
                 projects HAVE__BUILTIN_CLZ is used instead. */
              #ifdef __has_builtin
              # if __has_builtin(__builtin_clz)
              #  define HAVE_BUILTIN_CLZ 1
              # endif
              #elif defined(__GNUC__)
              # if (__GNUC__ > 3)
              #  define HAVE_BUILTIN_CLZ 1
              # elif defined(__GNUC_MINOR__)
              #  if (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
              #   define HAVE_BUILTIN_CLZ 1
              #  endif
              # endif
              #endif
              
              
              /**
               * Returns the largest power of two that is not greater than x. If x
               * is 0, returns 0.
               */
              unsigned int prev_power_of_2(unsigned int x)
              {
              #ifdef HAVE_BITSCANREVERSE
                  if (x <= 0) {
                      return 0;
                  } else {
                      unsigned long int index;
                      (void) _BitScanReverse(&index, x);
                      return (1U << index);
                  }
              #elif defined(HAVE_BUILTIN_CLZ)
                  if (x <= 0) {
                      return 0;
                  }
                  return (1U << (sizeof(x) * CHAR_BIT - 1 - __builtin_clz(x)));
              #else
                  /* Fastest known solution without compiler built-ins or integer
                     logarithm instructions.
                     From the book "Hacker's Delight".
                     Converted to a loop for smaller code size.
                     ("gcc -O3" will unroll this.) */
                  {
                      unsigned int shift;
                      for (shift = 1; shift < sizeof(x) * CHAR_BIT; shift <<= 1) {
                          x |= (x >> shift);
                      }
                  }
                  return (x - (x >> 1));
              #endif
              }
              
              unsigned long long prev_power_of_2_long_long(unsigned long long x)
              {
              #if (defined(HAVE_BITSCANREVERSE) && \
                  ULLONG_MAX == 18446744073709551615ULL)
                  if (x <= 0) {
                      return 0;
                  } else {
                      /* assert(sizeof(__int64) == sizeof(long long)); */
                      unsigned long int index;
                      (void) _BitScanReverse64(&index, x);
                      return (1ULL << index);
                  }
              #elif defined(HAVE_BUILTIN_CLZ)
                  if (x <= 0) {
                      return 0;
                  }
                  return (1ULL << (sizeof(x) * CHAR_BIT - 1 - __builtin_clzll(x)));
              #else
                  {
                      unsigned int shift;
                      for (shift = 1; shift < sizeof(x) * CHAR_BIT; shift <<= 1) {
                          x |= (x >> shift);
                      }
                  }
                  return (x - (x >> 1));
              #endif
              }
              

              【讨论】:

                【解决方案10】:

                当您以 2 为底数时,只需从右侧添加或删除一个数字,您就可以从 2 的幂跳到下一个。

                例如,数字 8 的 2 的前次幂是数字 4。二进制:

                01000 -> 0100(我们删除尾随的零以获得数字 4)

                所以求解先前2的幂的演算的算法是:

                previousPower := number shr 1

                previousPower = 数字 >> 1

                (或任何其他语法)

                【讨论】:

                • 只有当number 也是 2 的幂时才成立。如果 number 是 11,那么这将不起作用。
                【解决方案11】:

                这可以在一行中完成。

                int nextLowerPowerOf2 = i <= 0
                                        ? 0
                                        : ((i & (~i + 1)) == i)
                                            ? i >> 1
                                            : (1 << (int)Math.Log(i, 2));
                

                结果

                i    power_of_2
                -2    0
                -1    0
                0    0
                1    0
                2    1
                3    2
                4    2
                5    4
                6    4
                7    4
                8    4
                9    8
                

                这是一个更易读的 c# 版本,将

                int nextLowerPowerOf2 = IsPowerOfTwo(i) 
                    ? i >> 1 // shift it right
                    : GetPowerOfTwoLessThanOrEqualTo(i);
                
                public static int GetPowerOfTwoLessThanOrEqualTo(int x)
                {
                    return (x <= 0 ? 0 : (1 << (int)Math.Log(x, 2)));
                }
                
                public static bool IsPowerOfTwo(int x)
                {
                    return (((x & (~x + 1)) == x) && (x > 0));
                }
                

                【讨论】:

                  【解决方案12】:

                  下面的代码会找到2的前次幂:

                  int n = 100;
                      n /= 2;//commenting this will gives the next power of 2
                      n |= n>>1;
                      n |= n>>2;
                      n |= n>>4;
                      n |= n>>16;
                  
                  System.out.println(n+1);
                  

                  【讨论】:

                  • 其他答案非常相似,如果 n 已经是 2 的幂,则返回错误结果。
                  【解决方案13】:

                  这是我当前的解决方案,用于查找任何给定正整数 n 的两个的下一个和上一个幂,也是一个确定数字是否为 2 的幂的小函数。

                  此实现适用于 Ruby。

                  class Integer
                  
                    def power_of_two?
                      (self & (self - 1) == 0)
                    end
                  
                    def next_power_of_two
                      return 1 if self <= 0
                      val = self
                      val = val - 1
                      val = (val >> 1) | val
                      val = (val >> 2) | val
                      val = (val >> 4) | val
                      val = (val >> 8) | val
                      val = (val >> 16) | val
                      val = (val >> 32) | val if self.class == Bignum
                      val = val + 1
                    end
                  
                    def prev_power_of_two
                     return 1 if self <= 0
                     val = self
                     val = val - 1
                     val = (val >> 1) | val
                     val = (val >> 2) | val
                     val = (val >> 4) | val
                     val = (val >> 8) | val
                     val = (val >> 16) | val
                     val = (val >> 32) | val if self.class == Bignum
                     val = val - (val >> 1)
                    end
                  end
                  

                  使用示例:

                  10.power_of_two? => false
                  16.power_of_two? => true
                  10.next_power_of_two => 16
                  10.prev_power_of_two => 8
                  

                  对于前一个2的幂,找到下一个并除以2比上面的方法稍慢。

                  我不确定它如何与 Bignums 一起使用。

                  【讨论】:

                  • 嘿,礼仪有它,你应该把答案授予给你这个想法的人。我认为在这种情况下给自己正确的答案是不好的形式。
                  • 标记的答案是否对发帖人有所帮助?我认为标记的答案应该对其他有相同问题的人最有用。这篇文章用一个易于理解的答案总结了整个讨论。
                  猜你喜欢
                  • 2014-08-21
                  • 2014-03-01
                  • 2017-10-27
                  • 2015-04-30
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-12-03
                  • 1970-01-01
                  • 2012-03-19
                  相关资源
                  最近更新 更多