Pourquoi n'y a-t-il pas d'implémentation de plage de virgule flottante dans la bibliothèque standard?
Comme le montrent tous les articles ici, il n'y a pas de version en virgule flottante de range()
. Cela dit, l'omission a du sens si l'on considère que la range()
fonction est souvent utilisée comme un générateur d' index (et bien sûr, cela signifie un accesseur ). Ainsi, lorsque nous appelons range(0,40)
, nous disons en fait que nous voulons 40 valeurs commençant à 0, jusqu'à 40, mais non compris 40 lui-même.
Lorsque nous considérons que la génération d'index concerne autant le nombre d'indices que leurs valeurs, l'utilisation d'une implémentation float de range()
dans la bibliothèque standard a moins de sens. Par exemple, si nous appelions la fonction frange(0, 10, 0.25)
, nous nous attendrions à ce que 0 et 10 soient inclus, mais cela donnerait un vecteur avec 41 valeurs.
Ainsi, une frange()
fonction dépendant de son utilisation présentera toujours un comportement contre-intuitif; il a trop de valeurs perçues du point de vue de l'indexation ou ne comprend pas un nombre qui devrait raisonnablement être renvoyé du point de vue mathématique.
Le cas d'utilisation mathématique
Cela dit, comme discuté, numpy.linspace()
effectue bien la génération avec la perspective mathématique:
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.
])
Le cas d'utilisation de l'indexation
Et pour la perspective de l'indexation, j'ai écrit une approche légèrement différente avec une magie de chaîne astucieuse qui nous permet de spécifier le nombre de décimales.
# 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))
De même, nous pouvons également utiliser la round
fonction intégrée et spécifier le nombre de décimales:
# 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)
Une comparaison rapide et des performances
Bien entendu, compte tenu de la discussion ci-dessus, ces fonctions ont un cas d'utilisation assez limité. Néanmoins, voici une comparaison rapide:
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)
Les résultats sont identiques pour chacun:
-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
Et quelques horaires:
>>> 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
On dirait que la méthode de formatage de chaîne gagne par un cheveu sur mon système.
Les limites
Et enfin, une démonstration du point de la discussion ci-dessus et une dernière limitation:
# "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
De plus, lorsque le skip
paramètre n'est pas divisible par la stop
valeur, il peut y avoir un écart béant compte tenu de ce dernier problème:
# 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
Il existe des moyens de résoudre ce problème, mais en fin de compte, la meilleure approche serait probablement d'utiliser simplement Numpy.