Arbeiten mit Brüchen
Verfasst: Mi 3. Jan 2024, 15:51
Manchmal ist es angebracht, nicht mit Dezimalzahlen, sondern mit Brüchen zu arbeiten. Für diesen Zweck bietet Python die Klasse Fraction an. Leider steht diese - zumindest in meiner Firmware-Version - in Micropython nicht zur Verfügung. Glücklicherweise ist es nicht allzu aufwendig, eine solche Klasse zu programmieren. Nebenbei kann man beim Studium des Quellcodes vielleicht auch einiges dazulernen.
Beginnen wir mit der Definition des Konstruktors: Damit werden lediglich der Zähler z und der Nenner n zu einem Objekt zusammengefasst.
Mit der folgenden Methode kann man ein Fraction-Objekt in eine Zeichenkette der Form "z/n" umwandeln und damit auf dem Terminal ausgeben.
Nun können wir schon ein Fraction-Objekt erzeugen und auch ausgeben:
Nach dem Starten des Programms sehen wir im Terminal:
>>> %Run -c $EDITOR_CONTENT
126/777
Nun gilt es, Methoden zum Rechnen zu erstellen. Beginnen wir mit der Addition:
Der Code für die weiteren Rechenarten Subtraktion, Multiplikation und Division wird analog gebildet. Sie finden ihn auch weiter unten im Rahmen des Gesamtprogramms.
Wir wenden und jetzt lieber dem Kürzen zu. Sicherlich ist Ihnen schon aufgefallen, dass ich bei dem Code für die Addition nicht das kleinste gemeinsame Vielfache (kgV) der Nenner benutzt habe, sondern als gemeinsamen Nenner einfach das Produkt der beiden Nenner gewählt habe. Das ist recht einfach, hat aber den Nachteil, dass das Ergebnis (meist) nicht in gekürzter Form vorliegt. Einen Bruch können wir mit der im Folgenden dargestellten reduce-Methode kürzen; hierbei benutzen wir die Methode gcd (Greatest Common Divisor = größter gemeinsamer Teiler, kurz ggT):
Die Methode gcd greift auf den Euklidischen Algorithmus zurück; etwas transparenter wird die dort benutzte while-Schleife in der folgenden ausführlicheren (gleichwertigen) Form:
In der Kurzform nutzen wir auch aus, dass der Wert b = 0 von while als False gedeutet wird.
Jetzt können wir die Rechenfähigkeit des Programms einmal testen. Die Aufgabe lautet: 2/15 + 3/10; das Ergebnis soll in gekürzter Form angegeben werden. Der zugehörige Code lautet:
Auf dem Terminal sehen wir dann:
>>> %Run -c $EDITOR_CONTENT
2/15 + 3/10 = 13/30
Erwas gewöhnungsbedürftig ist die Asymmetrie bei der Addition: Der erste Summand ist hier der Besitzer der Methode __add__, der zweite Summand ein Parameter von __add__. Viel intuitiver wäre die Schreibweise
s = b1 + b2
Und tatsächlich ist diese Schreibweise auch möglich. Taucht zwischen zwei Objekten nämlich ein Plus-Zeichen auf, sucht Micropython automatisch im Quelltext nach einer Methode mit dem Namen __add__, setzt b1 und b2 entsprechend dort ein und übergibt den Rückgabewert an s. Die vereinfachende Schreibweise würde demnach nicht funktionieren, wenn wir unsere Additionsmethode z. B. __plus__ genannt hätten.
Für die anderen Rechenoperationen haben wir die zugehörigen Methoden ebenfalls so benannt, dass die Rechenzeichen -, * und / benutzt werden können. Auch der Methodenbezeichner __str__ für die Konvertierung eines Fraction-Objekts in eine Zeichenkette wurde ganz bewusst gewählt: Taucht ein solches Objekt nun in einem print-Befehl auf, so wird dieses Objekt automatisch mit Hilfe dieser Methode konvertiert.
Das gesamte Programm (Definition unserer Fraction-Klasse und Testbeispiele) sieht dann so aus:
Im Terminal werden folgende Ergebnisse angezeigt:
>>> %Run -c $EDITOR_CONTENT
126/777 = 6/37
2/15 + 3/10 = 13/30
2/15 * 3/10 = 1/25
2/15 * 3/10 = 1/25
2/15 - 3/10 = -1/6
2/15 : 3/10 = 4/9
Im Übrigen können Sie auch mit negative Brüchen arbeiten, z. B. mit Fraction(-3, 7). Das steht für -3/7.
Im Anhang befindet sich das Modul fraction.py für die Fraction-Klasse. Um es einzusetzen, muss
.
Beginnen wir mit der Definition des Konstruktors: Damit werden lediglich der Zähler z und der Nenner n zu einem Objekt zusammengefasst.
Code: Alles auswählen
class Fraction():
def __init__(self, z, n):
self.z = z # Zähler
self.n = n # Nenner
Code: Alles auswählen
def __str__(self): # Fraction Object > String
return str(self.z) + '/' + str(self.n)
Code: Alles auswählen
b0 = Fraction(126,777) # Bruch (Fraction-Objekt) erzeugen
print(b0.__str__())
>>> %Run -c $EDITOR_CONTENT
126/777
Nun gilt es, Methoden zum Rechnen zu erstellen. Beginnen wir mit der Addition:
Code: Alles auswählen
def __add__(self, other): # Addition
z = self.z*other.n + other.z*self.n
n = self.n * other.n
return Fraction(z,n)
Wir wenden und jetzt lieber dem Kürzen zu. Sicherlich ist Ihnen schon aufgefallen, dass ich bei dem Code für die Addition nicht das kleinste gemeinsame Vielfache (kgV) der Nenner benutzt habe, sondern als gemeinsamen Nenner einfach das Produkt der beiden Nenner gewählt habe. Das ist recht einfach, hat aber den Nachteil, dass das Ergebnis (meist) nicht in gekürzter Form vorliegt. Einen Bruch können wir mit der im Folgenden dargestellten reduce-Methode kürzen; hierbei benutzen wir die Methode gcd (Greatest Common Divisor = größter gemeinsamer Teiler, kurz ggT):
Code: Alles auswählen
def gcd(self): # ggT (Greatest Common Divisor)
# nach https://de.wikipedia.org/wiki/Euklidischer_Algorithmus
a = abs(self.z)
b = abs(self.n)
while b:
a, b = b, a%b
return a
def reduce(self): # Kürzen
__gcd = self.gcd()
self.z = self.z // __gcd
self.n = self.n // __gcd
if self.z < 0 and self.n <0:
self.z = - self.z
self.n = - self.n
return self
Code: Alles auswählen
while b != 0:
h = a % b # Rest der Division von a durch b
a = b
b = h
Jetzt können wir die Rechenfähigkeit des Programms einmal testen. Die Aufgabe lautet: 2/15 + 3/10; das Ergebnis soll in gekürzter Form angegeben werden. Der zugehörige Code lautet:
Code: Alles auswählen
b1 = Fraction(2,15)
b2 = Fraction(3,10)
s = b1.__add__(b2) # s. u.
s = s.reduce() # Der Ergebnisbruch wird gekürzt.
print(b1.__str__() + ' + ' + b2.__str__() + ' = ' + s.__str__())
Auf dem Terminal sehen wir dann:
>>> %Run -c $EDITOR_CONTENT
2/15 + 3/10 = 13/30
Erwas gewöhnungsbedürftig ist die Asymmetrie bei der Addition: Der erste Summand ist hier der Besitzer der Methode __add__, der zweite Summand ein Parameter von __add__. Viel intuitiver wäre die Schreibweise
s = b1 + b2
Und tatsächlich ist diese Schreibweise auch möglich. Taucht zwischen zwei Objekten nämlich ein Plus-Zeichen auf, sucht Micropython automatisch im Quelltext nach einer Methode mit dem Namen __add__, setzt b1 und b2 entsprechend dort ein und übergibt den Rückgabewert an s. Die vereinfachende Schreibweise würde demnach nicht funktionieren, wenn wir unsere Additionsmethode z. B. __plus__ genannt hätten.
Für die anderen Rechenoperationen haben wir die zugehörigen Methoden ebenfalls so benannt, dass die Rechenzeichen -, * und / benutzt werden können. Auch der Methodenbezeichner __str__ für die Konvertierung eines Fraction-Objekts in eine Zeichenkette wurde ganz bewusst gewählt: Taucht ein solches Objekt nun in einem print-Befehl auf, so wird dieses Objekt automatisch mit Hilfe dieser Methode konvertiert.
Das gesamte Programm (Definition unserer Fraction-Klasse und Testbeispiele) sieht dann so aus:
Code: Alles auswählen
class Fraction():
def __init__(self, z, n):
self.z = z # Zähler
self.n = n # Nenner
def __str__(self): # Fraction Object > String
return str(self.z) + '/' + str(self.n)
def gcd(self): # ggT (Greatest Common Divisor)
# nach https://de.wikipedia.org/wiki/Euklidischer_Algorithmus
a = abs(self.z)
b = abs(self.n)
while b:
a, b = b, a%b
return a
def reduce(self): # Kürzen
__gcd = self.gcd()
self.z = self.z // __gcd
self.n = self.n // __gcd
if self.z < 0 and self.n <0:
self.z = - self.z
self.n = - self.n
return self
def __add__(self, other): # Addition
z = self.z*other.n + other.z*self.n
n = self.n * other.n
return Fraction(z,n) # ggf. schon hier kürzen
def __sub__(self, other): # Subtraktion
z = self.z*other.n - other.z*self.n
n = self.n * other.n
return Fraction(z,n) # ggf. schon hier kürzen
def __mul__(self, other): # Multplikation
z = self.z * other.z
n = self.n * other.n
return Fraction(z,n) # ggf. schon hier kürzen
def __truediv__(self, other): # Division
z = self.z * other.n
n = self.n * other.z
return Fraction(z,n) # ggf. schon hier kürzen
# Testen...
b0 = Fraction(126,777) # Bruch (Fraction-Objekt) erzeugen
print(b0.__str__(), ' = ', end = '') # b0 in String konvertieren und zusammen mit '=' ausgeben
b0 = b0.reduce() # kürzen
print(b0) # Als Argument von print werden die Fraction-Objekte
# automatisch mit Hilfe von __str__(self) in Strings umgewandelt.
b1 = Fraction(2,15)
b2 = Fraction(3,10)
s = b1 + b2 # entspricht s = b1.__add__(b2)
s = s.reduce()
print(b1.__str__() + ' + ' + b2.__str__() + ' = ' + s.__str__())
p = b1 * b2 # entspricht p = b1.__mul__(b2)
p = p.reduce()
print(b1, '*', b2, '=', p) # oder...
print('%s * %s = %s' % (b1, b2, p))
d = b1 - b2 # entspricht d = b1.__sub__(b2)
d = d.reduce()
print('%s - %s = %s' % (b1, b2, d))
q = b1 / b2 # entspricht q = b1.__div__(b2)
q = q.reduce()
print('%s : %s = %s' % (b1, b2, q))
>>> %Run -c $EDITOR_CONTENT
126/777 = 6/37
2/15 + 3/10 = 13/30
2/15 * 3/10 = 1/25
2/15 * 3/10 = 1/25
2/15 - 3/10 = -1/6
2/15 : 3/10 = 4/9
Im Übrigen können Sie auch mit negative Brüchen arbeiten, z. B. mit Fraction(-3, 7). Das steht für -3/7.
Im Anhang befindet sich das Modul fraction.py für die Fraction-Klasse. Um es einzusetzen, muss
- dieses Modul in den Flash-Speicher geladen werden,
- die Fraction-Klasse importiert werden: from fraction import Fraction.
.