Nachdem Sie ja wissen, dass das Rechnen mit REALs nicht unbedingt
zum exakten Ergebnis führt, überlegen Sie sich einen Ausweg.
Ja, rationale Zahlen sind in Oberon noch nicht implementiert. Das
ist eine schöne Aufgabe, um auch Prozeduren kennenzulernen.
Sie bekommen einen Teil eines Oberon-Programms, das ein Taschenrechner
für rationale Zahlen werden soll. Implementieren Sie zunächst die
Prozeduren, die noch nicht implementiert
sind (wie in den Kommentaren beschrieben). Danach müssen sie
noch das Hauptprogramm für den Taschenrechner schreiben. Dieser Liest
Zeile für Zeile von der Standardeingabe bis zum Dateiende oder bis ein
Fehler auftritt. In der ersten Zeile steht der erste Bruch, mit dem
begonnen werden soll. In der darauffolgenden Zeile steht ein Operator
,,+``, ,,-``, ,,*`` oder ,,/``. Danach folgt wieder eine Zeile
mit einem Bruch, der auf den bisherigen (hier: Startwert aus der
ersten Zeile) addiert, von ihm subtrahiert, zu ihm multipliziert oder
durch den er dividiert werden soll. Danach folgt wieder eine Zeile
mit einem Operator und eine Zeile mit einem Bruch und so weiter.
Jeweils nach Eingabe einer Zahl
geben Sie den bisher berechneten Wert aus (als Bruch und REAL-Zahl;
siehe folgendes Beispiel). Brüche werden in der Form
zaehler/nenner
eingegeben (Bsp.: 1/3
, -5/10
).
Die Berechnungen erfolgen hier in der Reihenfolge, in der die
Operatoren eingegeben werden. (Es gilt nicht Punkt vor Strich!)
(Hinweis: Sie können davon ausgehen, dass die Eingabe korrekt ist.)
Beispiel für eine Ausführung dieses Programms (Ausgaben sind durch
,,>>
`` gekennzeichnet):
turing$ BruchRechner 1/2 >> 1/2 (= 5.000000D-0001) + 2/5 >> 9/10 (= 9.000000D-0001) - 1/6 >> 11/15 (= 7.333333D-0001) * 3/4 >> 11/20 (= 5.500000D-0001) / 7/2 >> 11/70 (= 1.571429D-0001) ^D turing$Der Taschenrechner hat bei obigem Beispiel folgende Berechnung durchgeführt:
Das folgende Grundgerüst von BruchRechner.om ist Ihnen vorgegeben:
MODULE BruchRechner; IMPORT Read, Write, Streams; TYPE Bruch = RECORD zaehler, nenner : LONGINT; END; VAR op : CHAR; acc : Bruch; PROCEDURE GGT(a, b: LONGINT): LONGINT; BEGIN IF (a = 0) OR (b = 0) THEN Write.StringS(Streams.stderr, "GGT: Parameter gleich 0"); Write.LnS(Streams.stderr); HALT(1); END; WHILE a # b DO IF a > b THEN a := a - b; ELSE b := b - a; END; END; RETURN a; END GGT; PROCEDURE KGV(a, b: LONGINT): LONGINT; BEGIN RETURN a * (b DIV GGT(a, b)); END KGV; PROCEDURE Kuerze(VAR erg: Bruch); (** kuerzt den Bruch erg; ist der Zaehler 0, so soll ** der Nenner den Wert 1 bekommen *) PROCEDURE Addiere(VAR erg: Bruch; a, b: Bruch); (** addiert a und b; das GEKUERZTE Ergebnis steht in erg **) PROCEDURE Subtrahiere(VAR erg: Bruch; a, b: Bruch); (** subtrahiert b von a; das GEKUERZTE Ergebnis steht in erg **) PROCEDURE Multipliziere(VAR erg: Bruch; a, b: Bruch); (** multipliziert a und b; das GEKUERZTE Ergebnis steht in erg **) PROCEDURE Dividiere(VAR erg: Bruch; a, b: Bruch); (** dividiert a und b; das GEKUERZTE Ergebnis steht in erg **) PROCEDURE KonvertiereBruch(a: Bruch): REAL; (** konvertiert a in eine REAL-Zahl und gibt diese zurueck **) PROCEDURE ReadBruch(VAR erg: Bruch); (** liest einen Bruch der Form [0-9]+/[0-9]+ (Bsp.: "1/3", "-10/15", ...) ** von der Standardeingabe ein und gibt diesen in GEKUERZTER ** Form in erg zurueck *) PROCEDURE WriteBruch(a: Bruch); (** gibt den Bruch a in der Form [0-9]+/[0-9]+ (Bsp.: "1/3", "-3/4", ...) ** auf die Standardausgabe aus *) BEGIN ReadBruch(acc); (* ersten Bruch einlesen *) Read.Ln; (* alles bis zum Zeilenende weglesen *) (** hier soll acc als Bruch und als REAL-Zahl ausgegeben werden **) Read.Char(op); (* Operator einlesen *) WHILE ~ Streams.stdin.eof DO Read.Ln; (* danach alle Zeichen bis (inkl.) Zeilenende weglesen *) (** hier soll zunaechst der Bruch b eingelesen werden; dann ** entsprechend dem Wert von op berechne: ** acc := acc + b oder acc := acc - b oder acc := acc * b ** oder acc := acc / b (!!! VORSICHT: symbolische Schreibweise !!!) ** bzw. gib Fehler aus, falls keiner der vier Operatoren ist ** danach Ausgabe von acc als Bruch UND als REAL-Zahl **) Read.Char(op); (* Operator einlesen *) END; END BruchRechner.
Lösung:
DEFINITION BruchRechner; END BruchRechner.
MODULE BruchRechner; IMPORT Read, Write, Streams; TYPE Bruch = RECORD zaehler, nenner : LONGINT; END; VAR op : CHAR; (* fuer den Operator *) acc, b : Bruch; PROCEDURE GGT(a, b: LONGINT): LONGINT; BEGIN IF (a = 0) OR (b = 0) THEN Write.StringS(Streams.stderr, "GGT: Parameter gleich 0"); Write.LnS(Streams.stderr); HALT(1); END; (* nur mit positiven Zahlen arbeiten *) a := ABS(a); b := ABS(b); WHILE a # b DO IF a > b THEN a := a - b; ELSE b := b - a; END; END; RETURN a; END GGT; PROCEDURE KGV(a, b: LONGINT): LONGINT; BEGIN RETURN a * (b DIV GGT(a, b)); END KGV; PROCEDURE Kuerze(VAR erg: Bruch); VAR ggt : LONGINT; BEGIN IF (erg.zaehler = 0) & (erg.nenner # 0) THEN erg.nenner := 1; ELSE ggt := GGT(erg.zaehler, erg.nenner); erg.zaehler := erg.zaehler DIV ggt; erg.nenner := erg.nenner DIV ggt; (* bei neg. Zahl, soll Nenner positiv und Zaehler negativ sein; * bei positiven Zahl sollen Zaehler und Nenner positiv sein *) IF erg.nenner < 0 THEN erg.zaehler := -erg.zaehler; erg.nenner := -erg.nenner; END; END; END Kuerze; PROCEDURE Addiere(VAR erg: Bruch; a, b: Bruch); BEGIN erg.nenner := KGV(a.nenner, b.nenner); (* Hauptnenner *) erg.zaehler := a.zaehler * (erg.nenner DIV a.nenner) + b.zaehler * (erg.nenner DIV b.nenner); Kuerze(erg); END Addiere; PROCEDURE Subtrahiere(VAR erg: Bruch; a, b: Bruch); BEGIN erg.nenner := KGV(a.nenner, b.nenner); (* Hauptnenner *) erg.zaehler := a.zaehler * (erg.nenner DIV a.nenner) - b.zaehler * (erg.nenner DIV b.nenner); Kuerze(erg); END Subtrahiere; PROCEDURE Multipliziere(VAR erg: Bruch; a, b: Bruch); BEGIN erg.zaehler := a.zaehler * b.zaehler; erg.nenner := a.nenner * b. nenner; Kuerze(erg); END Multipliziere; PROCEDURE Dividiere(VAR erg: Bruch; a, b: Bruch); BEGIN erg.zaehler := a.zaehler * b.nenner; erg.nenner := a.nenner * b.zaehler; Kuerze(erg); END Dividiere; PROCEDURE KonvertiereBruch(a: Bruch): REAL; BEGIN RETURN a.zaehler / a.nenner; END KonvertiereBruch; PROCEDURE ReadBruch(VAR erg: Bruch); VAR ch : CHAR; BEGIN Read.LongInt(erg.zaehler); Read.Char(ch); (* liest "/" weg *) Read.LongInt(erg.nenner); Kuerze(erg); END ReadBruch; PROCEDURE WriteBruch(a: Bruch); BEGIN Write.Int(a.zaehler, 0); Write.Char("/"); Write.Int(a.nenner, 0); END WriteBruch; BEGIN ReadBruch(acc); Read.Ln; Write.String(">> "); WriteBruch(acc); Write.String(" (= "); Write.Real(KonvertiereBruch(acc), 0); Write.Char(")"); Write.Ln; Read.Char(op); (* Operator einlesen *) WHILE ~ Streams.stdin.eof DO Read.Ln; (* nicht notwendig *) ReadBruch(b); Read.Ln; (* je nach gelesenem Zeichen die richtige Operation ausfuehren *) CASE op OF | "+" : Addiere(acc, acc, b); | "-" : Subtrahiere(acc, acc, b); | "*" : Multipliziere(acc, acc, b); | "/" : Dividiere(acc, acc, b); ELSE Write.StringS(Streams.stderr, "Unbekannte Operation"); Write.LnS(Streams.stderr); HALT(1); END; Write.String(">> "); WriteBruch(acc); Write.String(" (= "); Write.Real(KonvertiereBruch(acc), 0); Write.Char(")"); Write.Ln; Read.Char(op); END; END BruchRechner.