Data Science stellt sich in den Dienst der Mathematik

Seite 2: Genauigkeitsverlust beim Rechnen mit großen Zahlen

Inhaltsverzeichnis

Beim Einsatz von Python in der mathematischen Forschung gibt es einige Fallstricke, die man vermeiden sollte. So ist es bei der Verarbeitung großer Ganzzahlen notwendig, durchgängig den Datentyp int zu verwenden. Sobald eine Zahl in den Datentyp float konvertiert wird, geht die unbegrenzte Genauigkeit verloren und die Ergebnisse können sich verfälschen. Führt man beispielsweise eine Division mit nur einem Schrägstrich durch (siehe Listing 1), findet eine implizite Typkonvertierung in eine Fließkommazahl statt. Hierbei kommt es zum Genauigkeitsverlust. Richtig ist die Anwendung einer ganzzahligen Division mit zwei Schrägstrichen. Der passende Code dafür findet sich auf GitHub.

# Falscher Divisor verwendet
valid = 5**205 == (5**205 / 5) * 5
print("Division (Falsch):", valid)

# Richtiger Divisor verwendet 
valid = 5**205 == (5**205 // 5) * 5
print("Division (Richtig):", valid) 

Listing 1: Ganzzahlige Division

Der gleiche Effekt tritt auf, sobald Fließkommazahlen in Berechnungen einfließen (siehe Listing 2). Auch hier findet im Ergebnis eine Typkonvertierung mit Genauigkeitsverlust statt. Der Programmierer kann dies umgehen, indem er darauf achtet, nur Ganzzahlen (ohne Komma) in Berechnungen einzubeziehen.

# Falsch: Berechnung mit float
valid = 5**205 + 1 == 5**205 + 1.0
print("Weitere Berechnungen (Falsch):", valid) 

# Richtig: Berechnung mit int
valid = 5**205 + 1 == 5 ** 205 + 1
print("Weitere Berechnungen (Richtig):", valid)

Listing 2: Berechnungen mit Typkonvertierung

Auch das beliebte Framework Pandas erlaubt den Umgang und Berechnungen mit beliebig großen Ganzzahlen. Hierbei ist aber auch die Wahl des korrekten Datentyps wichtig. Im Gegensatz zu Pythons Datentyp int ist Pandas Datentyp int64 in seiner Größe begrenzt. Somit muss ein Programmierer verhindern, dass Python-Ganzzahlen in das Pendant von Pandas konvertiert werden.

Falsch ist beispielsweise die Berechnung großer Zahlen direkt in einem Series- oder DataFrame-Objekt wie in Listing 3. Richtig dagegen ist die Erzeugung großer Zahlen außerhalb des Pandas-Frameworks. Berechnet man Zahlen extern, werden sie bei der Erzeugung der Series oder des DataFrames im Datentyp object gespeichert. Die Genauigkeit bleibt in diesem Fall erhalten. Alternativ können Entwickler aber auch Zahlen mithilfe einer Lambda-Funktion im DataFrame berechnen (siehe Listing 4).

# Imports
import pandas as pd

# Falsch: Berechnung in Pandas
my_numbers = pd.Series([5, 6])
my_exponents = pd.Series([205, 206])
my_powers = my_numbers ** my_exponents
valid = list(my_powers) == [5**205, 6**206]
print("Berechnung in Pandas (Falsch):", valid)
print("Datatype:", my_powers.dtype)

# Richtig: Externe Berechnung 
my_powers = pd.Series([5**205, 6**206])
valid = list(my_powers) == [5**205, 6**206]
print("Berechnung in Pandas (Alternative 1):",valid)
print("Datatype:", my_powers.dtype)

Listing 3: Verarbeitung großer Zahlen in Pandas

# Richtig: Berechnung mit Lambda-Funktion
my_numbers = pd.Series([5, 6])
my_exponents = pd.Series([205, 206])
my_frame = pd.DataFrame({ 
  "number": my_numbers, "exponent": my_exponents})
my_frame["power"] = my_frame.apply( 
  lambda x: int(x["number"]) ** int(x["exponent"]), axis=1)
valid = list(my_frame["power"]) == [5**205, 6**206]
print("Berechnung in Pandas (Alternative 2):", valid)
print("Datatype:", my_frame["power"].dtype) 

Listing 4: Nutzung einer Lambda-Funktion in Pandas

Pandas DataFrames können mit relativ vielen Zahlen umgehen. Will man eine effiziente Verarbeitung erreichen, gilt es, Fallstricke zu vermeiden. So ist es beispielsweise nachteilig, iterativ einzelne Zahlen in einen DataFrame einzufügen (siehe Listing 5). Pandas aktualisiert bei diesem Vorgehen in jeder Iteration zahlreiche Attribute des Frames. Dies führt zum exponentiellen Anstieg der Verarbeitungszeit: Mit jeder Schleife wird das Programm langsamer. Das umgeht man durch ein iteratives Einfügen der Zahlen in eine Python-Liste, die man zum Schluss beim Erzeugen eines DataFrames als Ganzes übergibt (siehe Listing 5). Dieses Vorgehen benötigt einen Bruchteil der Verarbeitungszeit.

#Imports
import time
import pandas as pd

# Falsch: Iteratives Einf�gen
print("Einfuegen (Falsch) Start:", time.asctime())
my_numbers = pd.DataFrame({ 
  "number": []
}) 
for i in range(10000):
  my_numbers = my_numbers.append(pd.DataFrame({"number": [i]}), ignore_index=True) 
my_numbers.reset_index(drop=True)
print("Einfuegen (Falsch) Ende:", time.asctime())
print(my_numbers.head()) 

# Richtig: Einf�gen als Liste
print("Einfuegen (Richtig) Start:", time.asctime()) 
my_list = []
for i in range(10000): 
  my_list.append(i) 
my_numbers = pd.DataFrame({
  "number": my_list
}) 
print("Einfuegen (Richtig) Ende:", time.asctime()) 
print(my_numbers.head())

Listing 5: Viele Zahlen mit Pandas DataFrames verarbeiten