Warum gibt es in der Standardbibliothek keine Implementierung eines Gleitkommabereichs?
Wie aus allen Beiträgen hier hervorgeht, gibt es keine Gleitkommaversion von range()
. Das Auslassen ist jedoch sinnvoll, wenn man bedenkt, dass die range()
Funktion häufig als Indexgenerator (und natürlich als Accessor- Generator) verwendet wird. Wenn wir also anrufen range(0,40)
, sagen wir tatsächlich, wir wollen 40 Werte, die bei 0 beginnen, bis zu 40, aber ohne 40 selbst.
Wenn wir bedenken, dass es bei der Indexgenerierung sowohl um die Anzahl der Indizes als auch um deren Werte geht, ist die Verwendung einer Float-Implementierung range()
in der Standardbibliothek weniger sinnvoll. Wenn wir zum Beispiel die Funktion frange(0, 10, 0.25)
aufrufen würden, würden wir erwarten, dass sowohl 0 als auch 10 enthalten sind, aber das würde einen Vektor mit 41 Werten ergeben.
Daher zeigt eine frange()
Funktion in Abhängigkeit von ihrer Verwendung immer ein kontraintuitives Verhalten. Entweder hat es zu viele Werte, wie aus der Indexierungsperspektive wahrgenommen, oder es enthält keine Zahl, die aus mathematischer Sicht vernünftigerweise zurückgegeben werden sollte.
Der mathematische Anwendungsfall
Wie bereits erwähnt, numpy.linspace()
führt die Generierung die Generierung mit der mathematischen Perspektive gut durch:
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.
])
Der Anwendungsfall für die Indizierung
Und für die Indizierungsperspektive habe ich einen etwas anderen Ansatz mit etwas kniffliger String-Magie geschrieben, mit dem wir die Anzahl der Dezimalstellen angeben können.
# 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))
In ähnlicher Weise können wir auch die integrierte round
Funktion verwenden und die Anzahl der Dezimalstellen angeben:
# 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)
Ein schneller Vergleich und Leistung
Angesichts der obigen Diskussion haben diese Funktionen natürlich einen ziemlich begrenzten Anwendungsfall. Hier ein kurzer Vergleich:
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)
Die Ergebnisse sind jeweils identisch:
-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
Und einige Timings:
>>> 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
Sieht aus wie die String-Formatierungsmethode durch ein Haar auf meinem System gewinnt.
Die Einschränkungen
Und schließlich eine Demonstration des Punktes aus der obigen Diskussion und eine letzte Einschränkung:
# "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
Wenn der skip
Parameter nicht durch den stop
Wert teilbar ist, kann es bei letzterem Problem zu einer gähnenden Lücke kommen:
# 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
Es gibt Möglichkeiten, dieses Problem zu beheben, aber am Ende des Tages wäre es wahrscheinlich der beste Ansatz, nur Numpy zu verwenden.