Si como entrada proporciona la potencia (entera), ¿cuál es la forma más rápida de crear la potencia de diez correspondiente? Aquí hay cuatro alternativas que se me ocurren, y la forma más rápida parece ser usando una cuerda f:
from functools import partial from time import time import numpy as np def fstring(power): return float(f'1e{power}') def asterisk(power): return 10**power methods = { 'fstring': fstring, 'asterisk': asterisk, 'pow': partial(pow, 10), 'np.pow': partial(np.power, 10, dtype=float) } # "dtype=float" is necessary because otherwise it will raise: # ValueError: Integers to negative integer powers are not allowed. # see https://stackoverflow.com/a/43287598/5472354 powers = [int(i) for i in np.arange(-10000, 10000)] for name, method in methods.items(): start = time() for i in powers: method(i) print(f'{name}: {time() - start}')
Resultados:
fstring: 0.008975982666015625 asterisk: 0.5190775394439697 pow: 0.4863283634185791 np.pow: 0.046906232833862305
Supongo que el método f-string es el más rápido porque en realidad no se calcula nada, aunque solo funciona para potencias enteras de diez, mientras que los otros métodos son operaciones más complicadas que también funcionan con cualquier número real como base y potencia. Entonces, ¿el f-string es realmente la mejor manera de hacerlo?
Estás comparando manzanas con naranjas aquí. 10 ** n
calcula un número entero (cuando n
no es negativo), mientras que float(f'1e{n}')
calcula un número de coma flotante. Esos no tomarán la misma cantidad de tiempo, pero resuelven diferentes problemas, por lo que no importa cuál sea más rápido.
Pero es peor que eso, porque existe la sobrecarga de llamar a una función, que está incluida en su tiempo para todas sus alternativas, pero solo algunas de ellas realmente implican llamar a una función. Si escribe 10 ** n
, entonces no está llamando a una función, pero si usa partial(pow, 10)
entonces debe llamarlo como una función para obtener un resultado. Entonces, en realidad no estás comparando la velocidad de 10 ** n
de manera justa.
En lugar de ejecutar su propio código de tiempo, use la biblioteca timeit
, que está diseñada para hacer esto correctamente. Los resultados están en segundos para 1,000,000 de repeticiones (por defecto), o de manera equivalente, son el tiempo promedio en microsegundos para una repetición.
Aquí hay una comparación para calcular potencias enteras de 10:
>>> from timeit import timeit >>> timeit('10 ** n', setup='n = 500') 1.09881673199925 >>> timeit('pow(10, n)', setup='n = 500') 1.1821871869997267 >>> timeit('f(n)', setup='n = 500; from functools import partial; f = partial(pow, 10)') 1.1401332350014854
Y aquí hay una comparación para calcular potencias de punto flotante de 10: tenga en cuenta que calcular 10.0 ** 500
o 1e500
tiene sentido porque el resultado es simplemente un OverflowError
o inf
.
>>> timeit('10.0 ** n', setup='n = 200') 0.12391662099980749 >>> timeit('pow(10.0, n)', setup='n = 200') 0.17336435099969094 >>> timeit('f(n)', setup='n = 200; from functools import partial; f = partial(pow, 10.0)') 0.18887039500077663 >>> timeit('float(f"1e{n}")', setup='n = 200') 0.44305286100097874 >>> timeit('np.power(10.0, n, dtype=float)', setup='n = 200; import numpy as np') 1.491982370000187 >>> timeit('f(n)', setup='n = 200; from functools import partial; import numpy as np; f = partial(np.power, 10.0, dtype=float)') 1.6273324920002779
Entonces, la más rápida de estas opciones en ambos casos es la obvia: 10 ** n
para números enteros y 10.0 ** n
para flotantes.
Otro contendiente para el caso de los flotadores, calcule previamente todos los posibles resultados finitos distintos de cero y búsquelos:
0.0 if n < -323 else f[n] if n < 309 else inf
La preparación:
f = [10.0 ** i for i in [*range(309), *range(-323, 0)]] inf = float('inf')
Punto de referencia con el exponente de kaya3 n = 200
, así como n = -200
como exponente negativo con resultado distinto de cero y n = -5000
/ n = 5000
como exponentes negativos/positivos de tamaño mediano de su rango original:
n = 200 487 ns 487 ns 488 ns float(f'1e{n}') 108 ns 108 ns 108 ns 10.0 ** n 128 ns 129 ns 130 ns 10.0 ** n if n < 309 else inf 72 ns 73 ns 73 ns 0.0 if n < -323 else f[n] if n < 309 else inf n = -200 542 ns 544 ns 545 ns float(f'1e{n}') 109 ns 109 ns 110 ns 10.0 ** n 130 ns 130 ns 131 ns 10.0 ** n if n < 309 else inf 76 ns 76 ns 76 ns 0.0 if n < -323 else f[n] if n < 309 else inf n = -5000 291 ns 291 ns 291 ns float(f'1e{n}') 99 ns 99 ns 100 ns 10.0 ** n 119 ns 120 ns 120 ns 10.0 ** n if n < 309 else inf 34 ns 34 ns 34 ns 0.0 if n < -323 else f[n] if n < 309 else inf n = 5000 292 ns 293 ns 293 ns float(f'1e{n}') error error error 10.0 ** n 33 ns 33 ns 33 ns 10.0 ** n if n < 309 else inf 53 ns 53 ns 53 ns 0.0 if n < -323 else f[n] if n < 309 else inf
Código de referencia ( ¡Pruébelo en línea! ):
from timeit import repeat solutions = [ "float(f'1e{n}')", '10.0 ** n', '10.0 ** n if n < 309 else inf', '0.0 if n < -323 else f[n] if n < 309 else inf', ] for n in 200, -200, -5000, 5000: print(f'{n = }') setup = f''' n = {n} f = [10.0 ** i for i in [*range(309), *range(-323, 0)]] inf = float('inf') ''' for solution in solutions: try: ts = sorted(repeat(solution, setup))[:3] except OverflowError: ts = [None] * 3 print(*('%3d ns ' % (t * 1e3) if t else ' error ' for t in ts), solution) print()
Puede intentarlo con un enfoque logarítmico usando math.log y math.exp pero el rango de valores será limitado (que puede manejar con try/except).
Esto parece ser tan rápido como fstring, si no un poco más rápido.
import math ln10 = math.log(10) def mPow(power): try: return math.exp(ln10*power) except: return 0 if power<0 else math.inf
[EDITAR] Dado que estamos limitados por las capacidades de los flotadores, también podríamos preparar una lista con las 617 posibles potencias de 10 (que se pueden mantener en un flotador) y obtener la respuesta por índice:
import math minP10,maxP10 = -308,308 powersOf10 = [10**i for i in range(maxP10+1)]+[10**i for i in range(minP10,0)] def tenPower(power): if power < minP10: return 0 if power > maxP10: return math.inf return powersOf10[power] # negative indexes for powers -308...-1
Este es definitivamente más rápido que fstring