【问题标题】:Find the smallest power of 2 greater than or equal to n in Python在Python中找到大于或等于n的2的最小幂
【发布时间】:2021-05-30 12:39:12
【问题描述】:

在 Python 中返回大于或等于给定非负整数的 2 的最小幂的最简单函数是什么?

例如 2 大于等于 6 的最小幂是 8。

【问题讨论】:

    标签: python


    【解决方案1】:

    让我们测试一下:

    import collections
    import math
    import timeit
    
    def power_bit_length(x):
        return 2**(x-1).bit_length()
    
    def shift_bit_length(x):
        return 1<<(x-1).bit_length()
    
    def power_log(x):
        return 2**(math.ceil(math.log(x, 2)))
    
    def test(f):
        collections.deque((f(i) for i in range(1, 1000001)), maxlen=0)
    
    def timetest(f):
        print('{}: {}'.format(timeit.timeit(lambda: test(f), number=10),
                              f.__name__))
    
    timetest(power_bit_length)
    timetest(shift_bit_length)
    timetest(power_log)
    

    我使用range(1, 1000001) 而不仅仅是range(1000000) 的原因是power_log 版本在0 上会失败。我在较大范围内使用少量代表而不是在小范围内使用大量代表的原因是因为我希望不同的版本在不同的域上具有不同的性能。 (当然,如果您希望使用巨大的千位数来调用它,那么您需要一个使用这些的测试。)

    使用 Apple Python 2.7.2:

    4.38817000389: power_bit_length
    3.69475698471: shift_bit_length
    7.91623902321: power_log
    

    使用 Python.org Python 3.3.0:

    6.566169916652143: power_bit_length
    3.098236607853323: shift_bit_length
    9.982460380066186: power_log
    

    使用 pypy 1.9.0/2.7.2:

    2.8580930233: power_bit_length
    2.49524712563: shift_bit_length
    3.4371240139: power_log
    

    我相信这表明2** 是这里的慢部分;使用bit_length 代替log 确实可以加快速度,但使用1&lt;&lt; 代替2** 更为重要。

    另外,我认为它更清楚。 OP 的版本要求您从对数到位进行心理上下文切换,然后再返回指数。要么始终保持位 (shift_bit_length),要么保持在日志和指数中 (power_log)。

    【讨论】:

    • 请注意,x == 0 的结果不正确,因为 Python 中的 (-1).bit_length() == 1
    • 注意准确性。 math.log(2**29,2) 是 29.000000000000004,所以 power_log(2**29) 给出的错误答案是 30。
    • @ColonelPanic 如果使用math.log2,则不存在您提到的问题,这是理所当然的。仅当使用 math.log 时,从 29 开始存在问题。
    【解决方案2】:

    总是返回2**(x - 1).bit_length() 是不正确的,因为虽然它为 x=1 返回 1,但它为 x=0 返回一个非单调的 2。 x=0 单调安全的简单修复方法是:

    def next_power_of_2(x):  
        return 1 if x == 0 else 2**(x - 1).bit_length()
    

    示例输出:

    >>> print(', '.join(f'{x}:{next_power_of_2(x)}' for x in range(10)))
    0:1, 1:1, 2:2, 3:4, 4:4, 5:8, 6:8, 7:8, 8:8, 9:16
    

    由于2**float('-inf') == 0,x=0 应该返回 0(而不是 1)。

    【讨论】:

    • 对于大型 x 来说不是太慢了吗?除此之外,我不能说我明白。
    • @delnan -- 你为什么认为这会很慢? (也不是我理解代码......)
    • @delnan:首先,bit_length 有效地以 2 为底四舍五入 - 1,而且速度非常快。所以,提高 2 的幂,你就完成了。也许使用1 &lt;&lt; 而不是2 ** 会更快,但否则,您期望这里有多慢?
    • 我不确定python是如何实现bit_length的,但即使对于x的巨大值,它也几乎是即时的。
    • 没关系,我认为这是取 2 的 x-1th 次方,然后取其中的 bit_length。实际上是相反的。这样,中间整数会很快变大,但这样更合理。仍然不是我所说的直观。
    【解决方案3】:

    这对你有用吗:

    import math
    
    def next_power_of_2(x):
        return 1 if x == 0 else 2**math.ceil(math.log2(x))
    

    请注意,math.log2 在 Python 3 中可用,但在 Python 2 中不可用。使用它而不是 math.log 可以避免后者在 2**29 及以后出现数值问题。

    示例输出:

    >>> print(', '.join(f'{x}:{next_power_of_2(x)}' for x in range(10)))
    0:1, 1:1, 2:2, 3:4, 4:4, 5:8, 6:8, 7:8, 8:8, 9:16
    

    由于2**float('-inf') == 0,x=0 应该返回 0(而不是 1)。

    【讨论】:

    • 需要日志,我觉得比较慢。
    • @jhoyla 性能很少相关(缓慢的部分是查找两个函数并调用它们,而不是专门为log)。这绝对更具可读性和明显性(至少对我而言)。
    • 判断它是否更慢的唯一方法是测试......但它确实有一个缺点,它说 next_power_of_two(0)DomainError 而不是 1......
    • @jhoyla:哦,好点。每个版本都有一个简单的修复方法,但我不确定哪个版本修复后看起来更清晰......(另外,next_power_of_two(0) 应该是 0,而不是 1,因为0-inf,这是有争议的th 次幂,因此也是 -inf+1th... 但无论哪种方式,2 显然是错误的。)
    【解决方案4】:

    我们可以使用位操作来做到这一点:

    def next_power_of_2(n):
        if n == 0:
            return 1
        if n & (n - 1) == 0:
            return n
        while n & (n - 1) > 0:
            n &= (n - 1)
        return n << 1
    

    示例输出:

    >>> print(', '.join(f'{x}:{next_power_of_2(x)}' for x in range(10)))
    0:1, 1:1, 2:2, 3:4, 4:4, 5:8, 6:8, 7:8, 8:8, 9:16
    

    如需进一步阅读,请参阅this resource

    【讨论】:

      【解决方案5】:
      v+=(v==0);
      v--;
      v|=v>>1;
      v|=v>>2;
      v|=v>>4;
      v|=v>>8;
      v|=v>>16;
      v++;
      

      对于 16 位整数。

      【讨论】:

      • bit-twiddling hacks 很棒,但请引用来源
      【解决方案6】:

      嗯,我知道这个问题很老,我的答案很简单,但我真的很惊讶一直没有人在这里发布它,所以我将它作为答案发布。

      最简单的解决方案真的很简单,不需要导入任何库,使用while语句就可以一个循环!

      所以逻辑很简单,创建一个值为1的变量,而变量的值小于数字,将变量乘以2!

      代码:

      i = 1
      while i < n: i *=2
      

      n = 1、63、64、255、256、1000、4095 的返回值:

      2, 64, 64, 256, 256, 1024, 4096
      

      这可以很容易地修改为计算下一个电源柜到许多其他基地:

      def nextpower(num, base):
        i = 1
        while i < num: i *= base
        return i
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-09
        • 2016-09-30
        • 1970-01-01
        • 1970-01-01
        • 2010-09-26
        • 2012-01-06
        相关资源
        最近更新 更多