¿Por qué no hay implementación de rango de punto flotante en la biblioteca estándar?
Como lo aclaran todas las publicaciones aquí, no hay una versión de coma flotante range()
. Dicho esto, la omisión tiene sentido si consideramos que la range()
función se usa a menudo como un generador de índice (y, por supuesto, eso significa un accesor ). Entonces, cuando llamamos range(0,40)
, en efecto estamos diciendo que queremos 40 valores que comiencen en 0, hasta 40, pero que no incluyan 40 en sí.
Cuando consideramos que la generación de índices tiene tanto que ver con el número de índices como con sus valores, el uso de una implementación flotante range()
en la biblioteca estándar tiene menos sentido. Por ejemplo, si llamamos a la función frange(0, 10, 0.25)
, esperaríamos que se incluyan tanto 0 como 10, pero eso produciría un vector con 41 valores.
Por lo tanto, una frange()
función que depende de su uso siempre exhibirá un comportamiento contrario a la intuición; o tiene demasiados valores como se perciben desde la perspectiva de indexación o no incluye un número que razonablemente debería ser devuelto desde la perspectiva matemática.
El caso de uso matemático
Dicho esto, como se discutió, numpy.linspace()
realiza la generación con la perspectiva matemática muy bien:
numpy.linspace(0, 10, 41)
array([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75,
2. , 2.25, 2.5 , 2.75, 3. , 3.25, 3.5 , 3.75,
4. , 4.25, 4.5 , 4.75, 5. , 5.25, 5.5 , 5.75,
6. , 6.25, 6.5 , 6.75, 7. , 7.25, 7.5 , 7.75,
8. , 8.25, 8.5 , 8.75, 9. , 9.25, 9.5 , 9.75, 10.
])
El caso de uso de indexación
Y para la perspectiva de indexación, he escrito un enfoque ligeramente diferente con un poco de magia con cuerdas engañosa que nos permite especificar el número de lugares decimales.
# Float range function - string formatting method
def frange_S (start, stop, skip = 1.0, decimals = 2):
for i in range(int(start / skip), int(stop / skip)):
yield float(("%0." + str(decimals) + "f") % (i * skip))
Del mismo modo, también podemos usar la round
función incorporada y especificar el número de decimales:
# Float range function - rounding method
def frange_R (start, stop, skip = 1.0, decimals = 2):
for i in range(int(start / skip), int(stop / skip)):
yield round(i * skip, ndigits = decimals)
Una comparación rápida y rendimiento
Por supuesto, dada la discusión anterior, estas funciones tienen un caso de uso bastante limitado. Sin embargo, aquí hay una comparación rápida:
def compare_methods (start, stop, skip):
string_test = frange_S(start, stop, skip)
round_test = frange_R(start, stop, skip)
for s, r in zip(string_test, round_test):
print(s, r)
compare_methods(-2, 10, 1/3)
Los resultados son idénticos para cada uno:
-2.0 -2.0
-1.67 -1.67
-1.33 -1.33
-1.0 -1.0
-0.67 -0.67
-0.33 -0.33
0.0 0.0
...
8.0 8.0
8.33 8.33
8.67 8.67
9.0 9.0
9.33 9.33
9.67 9.67
Y algunos horarios:
>>> import timeit
>>> setup = """
... def frange_s (start, stop, skip = 1.0, decimals = 2):
... for i in range(int(start / skip), int(stop / skip)):
... yield float(("%0." + str(decimals) + "f") % (i * skip))
... def frange_r (start, stop, skip = 1.0, decimals = 2):
... for i in range(int(start / skip), int(stop / skip)):
... yield round(i * skip, ndigits = decimals)
... start, stop, skip = -1, 8, 1/3
... """
>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))
0.024284090992296115
>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))
0.025324633985292166
Parece que el método de formato de cadena gana por un pelo en mi sistema.
Las limitaciones
Y finalmente, una demostración del punto de la discusión anterior y una última limitación:
# "Missing" the last value (10.0)
for x in frange_R(0, 10, 0.25):
print(x)
0.25
0.5
0.75
1.0
...
9.0
9.25
9.5
9.75
Además, cuando el skip
parámetro no es divisible por el stop
valor, puede haber una brecha enorme debido al último problema:
# Clearly we know that 10 - 9.43 is equal to 0.57
for x in frange_R(0, 10, 3/7):
print(x)
0.0
0.43
0.86
1.29
...
8.14
8.57
9.0
9.43
Hay formas de abordar este problema, pero al final del día, el mejor enfoque probablemente sería simplemente usar Numpy.