Perché non esiste un'implementazione dell'intervallo in virgola mobile nella libreria standard?
Come chiarito da tutti i post qui, non esiste una versione in virgola mobile di range(). Detto questo, l'omissione ha senso se consideriamo che la range()funzione viene spesso utilizzata come generatore di indice (e, naturalmente, ciò significa un accessore ). Quindi, quando chiamiamo range(0,40), stiamo in effetti dicendo che vogliamo 40 valori a partire da 0, fino a 40, ma non inclusivi del 40 stesso.
Se consideriamo che la generazione di indici riguarda tanto il numero di indici quanto i loro valori, l'uso di un'implementazione float range()nella libreria standard ha meno senso. Ad esempio, se abbiamo chiamato la funzionefrange(0, 10, 0.25) , ci saremmo aspettati che fossero inclusi sia 0 che 10, ma ciò avrebbe prodotto un vettore con 41 valori.
Pertanto, una frange()funzione che dipende dal suo uso mostrerà sempre un comportamento contro intuitivo; o ha troppi valori come percepiti dalla prospettiva dell'indicizzazione o non è comprensivo di un numero che ragionevolmente dovrebbe essere restituito dalla prospettiva matematica.
Il caso di uso matematico
Detto questo, come discusso, numpy.linspace()esegue bene la generazione con la prospettiva matematica:
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.
])
Il caso d'uso dell'indicizzazione
E per la prospettiva di indicizzazione, ho scritto un approccio leggermente diverso con alcune magiche stringhe che ci permettono di specificare il numero di cifre decimali.
# 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))
Allo stesso modo, possiamo anche usare la roundfunzione integrata e specificare il numero di decimali:
# 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)
Un rapido confronto e prestazioni
Naturalmente, vista la discussione di cui sopra, queste funzioni hanno un caso d'uso piuttosto limitato. Tuttavia, ecco un rapido confronto:
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)
I risultati sono identici per ciascuno:
-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
E alcuni tempi:
>>> 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
Sembra che il metodo di formattazione delle stringhe vince da un capello sul mio sistema.
Le limitazioni
E infine, una dimostrazione del punto della discussione sopra e un'ultima limitazione:
# "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
Inoltre, quando il skipparametro non è divisibile per il stopvalore, può esserci un divario di sbadiglio dato quest'ultimo 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
Ci sono modi per affrontare questo problema, ma alla fine, l'approccio migliore sarebbe probabilmente quello di usare solo Numpy.