在Python中计算快速的log base 2天花板

eyh26e7m  于 2023-04-19  发布在  Python
关注(0)|答案(7)|浏览(125)

对于给定的x < 10^15,快速准确地确定最大整数p,使得2^p <= x
以下是我尝试过的一些方法:
首先,我尝试了这个,但它对大数字不准确:

>>> from math import log
>>> x = 2**3
>>> x
8
>>> p = int(log(x, 2))
>>> 2**p == x
True
>>> x = 2**50
>>> p = int(log(x, 2))
>>> 2**p == x #not accurate for large numbers?
False

我可以试试这样的:

p = 1
i = 1
while True:
    if i * 2 > n:
        break
    i *= 2
    p += 1
    not_p = n - p

如果p是50,这将需要50次操作
我可以预先计算2的所有幂,直到2^50,然后使用二进制搜索来找到p。这将需要大约log(50)的操作,但似乎有点过分和丑陋?
我找到了这个基于C的解决方案的线程:Compute fast log base 2 ceiling
然而,它似乎有点丑陋,我不太确定如何将其转换为Python。

jtjikinw

jtjikinw1#

在Python〉= 2.7中,你可以使用整数的.bit_length()方法:

def brute(x):
    # determine max p such that 2^p <= x
    p = 0
    while 2**p <= x:
        p += 1
    return p-1

def easy(x):
    return x.bit_length() - 1

它给出了

>>> brute(0), brute(2**3-1), brute(2**3)
(-1, 2, 3)
>>> easy(0), easy(2**3-1), easy(2**3)
(-1, 2, 3)
>>> brute(2**50-1), brute(2**50), brute(2**50+1)
(49, 50, 50)
>>> easy(2**50-1), easy(2**50), easy(2**50+1)
(49, 50, 50)
>>> 
>>> all(brute(n) == easy(n) for n in range(10**6))
True
>>> nums = (max(2**x+d, 0) for x in range(200) for d in range(-50, 50))
>>> all(brute(n) == easy(n) for n in nums)
True
wwodge7n

wwodge7n2#

你在注解中指定x是一个整数,但是对于任何来到这里的人来说,他们的x已经是一个float,那么**math.frexp()**在提取以2为底的日志时会非常快:

log2_slow = int(floor(log(x, 2)))
log2_fast = frexp(x)[1]-1

C function that frexp() calls只是抓取和调整指数。更多的解释:

  • 下标[1]是因为frexp()返回一个元组(有效数,指数)。
  • 减法-1说明有效位在[0.5,1.0)范围内。例如,250存储为0.5x251。
  • floor()是因为你指定了2^p <= x,所以是p == floor(log(x,2))

(来源于another answer

30byixjq

30byixjq3#

注意!接受的答案返回floor(log(n, 2)),而不是像问题标题所暗示的那样返回ceil(log(n, 2))
如果你来这里是为了一个clog 2的实现,请这样做:

def clog2(x):
    """Ceiling of log2"""
    if x <= 0:
        raise ValueError("domain error")
    return (x-1).bit_length()

为了完整:

def flog2(x):
    """Floor of log2"""
    if x <= 0:
        raise ValueError("domain error")
    return x.bit_length() - 1
tct7dpnv

tct7dpnv4#

你可以尝试numpy中的log2函数,它似乎适用于高达2^62的幂:

>>> 2**np.log2(2**50) == 2**50
True
>>> 2**np.log2(2**62) == 2**62
True

除此之外(至少对我来说),由于numpy的内部数字类型的限制,它失败了,但它将处理你所说的范围内的数据。

gdrx4gfi

gdrx4gfi5#

对我来说,OSX 10.7上的Python 2.6.5(CPython)很好用:

>>> x = 2**50
>>> x
1125899906842624L
>>> p = int(log(x,2))
>>> p
50
>>> 2**p == x
True

它至少对指数1e9仍然有效,到那时它开始需要相当长的时间来做数学运算。在测试中,xp实际上得到了什么?你运行的是什么版本的Python,在什么操作系统上?

4ioopgfo

4ioopgfo6#

关于“对大数字不准确”,你在这里的挑战是浮点表示确实没有你需要的那么精确(49.999999999993 != 50.0)。
好消息是,C例程的转换非常简单:

def getpos(value):
    if (value == 0):
        return -1
    pos = 0
    if (value & (value - 1)):
        pos = 1
    if (value & 0xFFFFFFFF00000000):
        pos += 32
        value = value >> 32
    if (value & 0x00000000FFFF0000):
        pos += 16
        value = value >> 16
    if (value & 0x000000000000FF00):
        pos += 8
        value = value >> 8
    if (value & 0x00000000000000F0):
        pos += 4
        value = value >> 4
    if (value & 0x000000000000000C):
        pos += 2
        value = value >> 2
    if (value & 0x0000000000000002):
        pos += 1
        value = value >> 1
    return pos

另一种方法是,你可以四舍五入到最接近的整数,而不是截断:

log(x,2)
=> 49.999999999999993
   round(log(x,2),1)
=> 50.0
5cnsuln7

5cnsuln77#

我需要计算2的上界幂(计算出使用模运算符在给定范围内生成一个随机数需要多少字节的熵)。
从一个粗略的实验中,我认为下面的计算给出了最小整数p,使得瓦尔〈2^p
它可能和你能得到的一样快,并且只使用按位整数运算。

def log2_approx(val):
    from math import floor
    val = floor(val)
    approx = 0
    while val != 0:
        val &= ~ (1<<approx)
        approx += 1
    return approx

对于给定的n,您的稍微不同的值将通过以下方式计算

log2_approx(n) - 1

...也许吧。但无论如何,按位算术可以给予你一个如何快速完成这个操作的线索。

相关问题