在没有数学模块的情况下找到以数字为底 2 的算法
Find algorithm to base 2 of number without math module
在不使用 math
模块的情况下求给定数字以 2 为底的对数的最有效方法是什么?
math.log
和 math.log2
是浮点运算,因此存在固有的准确性问题,可能会给出错误的结果,但它们非常快。
保证数字是2的幂,函数只有满足以下函数才会执行 returns True
:
def power_of_2(n):
return (n & (n-1) == 0) and n != 0
我希望结果 100% 准确,我已经找到了实现此目标的方法:
def log2(n):
i = 0
while n > 1:
n >>= 1
i += 1
return i
表现:
In [244]: def log2(n):
...: i = 0
...: while n > 1:
...: n >>= 1
...: i += 1
...: return i
In [245]: log2(33554432)
Out[245]: 25
In [246]: import math
In [247]: math.log2(33554432)
Out[247]: 25.0
In [248]: %timeit log2(33554432)
2.37 µs ± 208 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [249]: %timeit math.log2(33554432)
125 ns ± 1.34 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
我发现位操作比浮点操作更适合此任务,如何在使用相同逻辑的同时提高函数的性能?
如果您的代码需要重复调用此函数,并且输入保证是 2 的偶数次整数次方(与您的示例不同!),我想很难击败硬编码字典查找。
def precalculate_pow2(ceiling):
i = 1
result = dict()
for n in range(ceiling):
result[i] = n
i <<= 1
return result
powersof2 = precalculate_pow2(500) # or whatever your upper limit is
def log2(n):
return powersof2[n]
显然,计算值的循环并不比您当前的代码更有效,但是将其记忆化可以让您分摊对 log2
函数的所有调用的成本。
作为一种变体,您可以从仅包含一个值的字典开始,并使用递归函数仅在后续调用中填充缺失的部分。
powersof2 = {1: 0}
def log2(n):
if n not in powersof2:
powersof2[n] = 1 + log2(n >> 1)
return powersof2[n]
这有一个明显的缺点,如果你用负值调用它,它会进入无限循环(尽管 Python 会在你用尽最大递归限制后抛出回溯)。
我已经尝试 运行 来自 1 and 2 的不同答案的大多数方法,我发现 power_of_2
和 easy
表现出良好的性能。
import math
import timeit
def myLog(x, b):
if x < b:
return 0
return 1 + float(myLog(x / b, b))
def easy(x):
return x.bit_length() - 1
def brute(x):
# determine max p such that 2^p <= x
p = 0
while 2 ** p <= x:
p += 1
return p - 1
def log2_approx(val):
from math import floor
val = floor(val)
approx = 0
while val != 0:
val &= ~ (1 << approx)
approx += 1
return approx
def log2(n):
i = 0
while n > 1:
n >>= 1
i += 1
return i
def power_of_2(n):
return (n & (n - 1) == 0) and n != 0
def log2_fast(n):
return math.frexp(33554436)[1] - 1
import time
from functools import partial
import time
start_time = time.time()
math.log2(33554432)
print("math.log2 is %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
myLog(33554432.0, 2.0)
print(" myLog is %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
brute(33554432)
print(" brute is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
log2_approx(33554432) - 1
print("log2_approx is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
log2_fast = math.frexp(33554436)[1] - 1
print(" log2_fast is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
log2(33554432)
print("log2 is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
power_of_2(33554432)
print("power_of_2 is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
easy(33554432)
print("easy is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
运行 时间
math.log2 is 3.0994415283203125 microseconds ---
myLog is 5.4836273193359375 microseconds ---
brute is --- 6.4373016357421875 microseconds ---
log2_approx is --- 6.4373016357421875 microseconds ---
log2_fast is --- 1.6689300537109375 microseconds ---
log2 is --- 2.1457672119140625 microseconds ---
power_of_2 is --- 0.7152557373046875 microseconds ---
easy is --- 0.476837158203125 microseconds ---
使用timeit
显示延迟
test_items=33554432
base=2
times = timeit.Timer(partial(math.log2, test_items))
print("math.log2 is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(myLog, test_items,base))
print("myLog is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(easy, test_items))
print("easy is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(brute, test_items))
print("brute is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(log2_approx, test_items))
print("log2_approx is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(log2, test_items))
print("log2 is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(power_of_2, test_items))
print("power_of_2 is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(log2_fast, test_items))
print("log2_fast is %s microseconds ---", (times.timeit(1000000)))
运行 时间
math.log2 is %s microseconds --- 0.05126429593656212
myLog is %s microseconds --- 4.137887543998659
easy is %s microseconds --- 0.10356121498625726
brute is %s microseconds --- 5.254867412033491
log2_approx is %s microseconds --- 3.81522585300263
log2 is %s microseconds --- 1.7966924259671941
power_of_2 is %s microseconds --- 0.1572157460032031
log2_fast is %s microseconds --- 0.21886748599354178
在不使用 math
模块的情况下求给定数字以 2 为底的对数的最有效方法是什么?
math.log
和 math.log2
是浮点运算,因此存在固有的准确性问题,可能会给出错误的结果,但它们非常快。
保证数字是2的幂,函数只有满足以下函数才会执行 returns True
:
def power_of_2(n):
return (n & (n-1) == 0) and n != 0
我希望结果 100% 准确,我已经找到了实现此目标的方法:
def log2(n):
i = 0
while n > 1:
n >>= 1
i += 1
return i
表现:
In [244]: def log2(n):
...: i = 0
...: while n > 1:
...: n >>= 1
...: i += 1
...: return i
In [245]: log2(33554432)
Out[245]: 25
In [246]: import math
In [247]: math.log2(33554432)
Out[247]: 25.0
In [248]: %timeit log2(33554432)
2.37 µs ± 208 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [249]: %timeit math.log2(33554432)
125 ns ± 1.34 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
我发现位操作比浮点操作更适合此任务,如何在使用相同逻辑的同时提高函数的性能?
如果您的代码需要重复调用此函数,并且输入保证是 2 的偶数次整数次方(与您的示例不同!),我想很难击败硬编码字典查找。
def precalculate_pow2(ceiling):
i = 1
result = dict()
for n in range(ceiling):
result[i] = n
i <<= 1
return result
powersof2 = precalculate_pow2(500) # or whatever your upper limit is
def log2(n):
return powersof2[n]
显然,计算值的循环并不比您当前的代码更有效,但是将其记忆化可以让您分摊对 log2
函数的所有调用的成本。
作为一种变体,您可以从仅包含一个值的字典开始,并使用递归函数仅在后续调用中填充缺失的部分。
powersof2 = {1: 0}
def log2(n):
if n not in powersof2:
powersof2[n] = 1 + log2(n >> 1)
return powersof2[n]
这有一个明显的缺点,如果你用负值调用它,它会进入无限循环(尽管 Python 会在你用尽最大递归限制后抛出回溯)。
我已经尝试 运行 来自 1 and 2 的不同答案的大多数方法,我发现 power_of_2
和 easy
表现出良好的性能。
import math
import timeit
def myLog(x, b):
if x < b:
return 0
return 1 + float(myLog(x / b, b))
def easy(x):
return x.bit_length() - 1
def brute(x):
# determine max p such that 2^p <= x
p = 0
while 2 ** p <= x:
p += 1
return p - 1
def log2_approx(val):
from math import floor
val = floor(val)
approx = 0
while val != 0:
val &= ~ (1 << approx)
approx += 1
return approx
def log2(n):
i = 0
while n > 1:
n >>= 1
i += 1
return i
def power_of_2(n):
return (n & (n - 1) == 0) and n != 0
def log2_fast(n):
return math.frexp(33554436)[1] - 1
import time
from functools import partial
import time
start_time = time.time()
math.log2(33554432)
print("math.log2 is %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
myLog(33554432.0, 2.0)
print(" myLog is %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
brute(33554432)
print(" brute is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
log2_approx(33554432) - 1
print("log2_approx is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
log2_fast = math.frexp(33554436)[1] - 1
print(" log2_fast is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
log2(33554432)
print("log2 is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
power_of_2(33554432)
print("power_of_2 is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
start_time = time.time()
easy(33554432)
print("easy is --- %s microseconds ---" % ((time.time() - start_time) * 1000000))
运行 时间
math.log2 is 3.0994415283203125 microseconds ---
myLog is 5.4836273193359375 microseconds ---
brute is --- 6.4373016357421875 microseconds ---
log2_approx is --- 6.4373016357421875 microseconds ---
log2_fast is --- 1.6689300537109375 microseconds ---
log2 is --- 2.1457672119140625 microseconds ---
power_of_2 is --- 0.7152557373046875 microseconds ---
easy is --- 0.476837158203125 microseconds ---
使用timeit
显示延迟
test_items=33554432
base=2
times = timeit.Timer(partial(math.log2, test_items))
print("math.log2 is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(myLog, test_items,base))
print("myLog is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(easy, test_items))
print("easy is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(brute, test_items))
print("brute is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(log2_approx, test_items))
print("log2_approx is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(log2, test_items))
print("log2 is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(power_of_2, test_items))
print("power_of_2 is %s microseconds ---", (times.timeit(1000000)))
times = timeit.Timer(partial(log2_fast, test_items))
print("log2_fast is %s microseconds ---", (times.timeit(1000000)))
运行 时间
math.log2 is %s microseconds --- 0.05126429593656212
myLog is %s microseconds --- 4.137887543998659
easy is %s microseconds --- 0.10356121498625726
brute is %s microseconds --- 5.254867412033491
log2_approx is %s microseconds --- 3.81522585300263
log2 is %s microseconds --- 1.7966924259671941
power_of_2 is %s microseconds --- 0.1572157460032031
log2_fast is %s microseconds --- 0.21886748599354178