1. Mathematisches
Schon in den ersten Aufgaben kamen Ganzzahlen und Fließkommazahlen vor; wir haben die Zahlen mit den üblichen mathematischen Operatoren verrechnet. Nicht für alles hat Java einen Operator, und so bietet die Klasse Math
weitere Methoden und Konstanten; schon früh haben wir zum Beispiel Math.random()
eingesetzt. In den folgenden Aufgaben dieses Kapitels geht es insbesondere um verschiedene Rundungen, und wir werden auf Methoden der Klasse Math
schauen. Zudem bietet das Paket java.math
zwei Klassen, mit denen sich beliebig große Zahlen repräsentieren lassen — auch dazu gibt es Aufgaben.
Voraussetzungen
Rundungsmethoden der Klasse
Math
kennenBigInteger
undBigDecimal
kennen
Verwendete Datentypen in diesem Kapitel:
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹
1.1. Die Klasse Math
Die Klasse Math
enthält eine große Anzahl mathematischer Funktionen, aber es ist wichtig, auch zum Beispiel bei Wrapper-Klassen zu schauen oder beim Scanner
oder Formatter
, wenn es darum geht, eine Zahl in einen String zu konvertieren oder einen String zu parsen, sodass am Ende wieder ein primitiver Typ steht.
1.1.1. Prüfen, ob Tin Tin beim Runden betrogen hat ⭐
Die Buchhalterin Tin Tin macht für Captain CiaoCiao die Aufstellungen der Ein- und Ausgaben. Sie bekommt positive und negative Fließkommawerte und schreibt für eine Bilanz zum Schluss die Summe als gerundete Ganzzahl auf. Captain CiaoCiao hat den Verdacht, dass Tin Tin nicht ganz ehrlich ist und sich die Centbeträge unter den Nagel reißt, wobei sie die Summen doch korrekt kaufmännisch runden sollte. Zur Erinnerung: Ist die Zahl an der ersten wegfallenden Dezimalstelle eine 0, 1, 2, 3 oder 4, wird abgerundet; ist sie 5, 6, 7, 8 oder 9, wird aufgerundet.
Um seine Vermutung zu prüfen, benötigt Captain CiaoCiao ein Programm, das summieren und verschiedene Rundungsmodi prüfen kann.
Aufgabe:
Gegeben sind ein Array mit Fließkommazahlen (positiv und negativ) und die von Tin Tin in eine Ganzzahl konvertierte Summe.
Captain CiaoCiao möchte herausfinden, mit welcher Rundung die Ganzzahl der Summe gebildet wurde. Daher sollen die Elemente in dem Array summiert werden und mit der Summe von Tin Tin verglichen werden. Die Rundung wird erst nach der Addition der Zahlen durchgeführt.
Implementiere eine Methode
RoundingMode detectRoundingMode(double[] numbers, int sum)
, die eindouble
-Array mit Zahlen und die Summe von Tin Tin bekommt und prüft, welcher Rundungsmodus verwendet wurde.Damit sich der Rundungsmodus repräsentieren lässt, führe einen Aufzählungstyp ein:
enum RoundingMode { CAST, ROUND, FLOOR, CEIL, RINT, UNKNOWN; }
Die Aufzählungselemente repräsentieren unterschiedliche Rundungsarten:
(int)
, also eine Typumwandlung(int) Math.floor(double)
(int) Math.ceil(double)
(int) Math.rint(double)
(int) Math.round(double)
Welche Rundung ist für Captain CiaoCiao schlecht und für Tin Tin gut? Mit welcher Variante könnte Tin Tin betrügen?
Beispiel:
Der Aufruf könnte so aussehen:
double[] numbers = { 199.99 }; System.out.println( detectRoundingMode( numbers, 200 ) );
Hinweise:
Es gibt im Paket
java.math
den AufzählungstypRoundingMode
, doch für unseren Fall passt er nicht auf die Aufgabe.Es kann gut passieren, dass mehrere Rundungsmodi passen — etwa wenn die Summe der Fließkommawerte selbst eine Ganzzahl ergibt — dann kann die Methode sich für einen der Rundungsmodi frei entscheiden.
1.2. Große und sehr präzise Zahlen
Mit den Klassen java.math.BigInteger
und java.math.BigDecimal
lassen sich beliebig große Ganz- und Fließkommazahlen repräsentieren.
1.2.1. Arithmetischen Mittelwert einer großen Ganzzahl berechnen ⭐
Um den arithmetischen Mittelwert von zwei Zahlen zu berechnen, werden sie addiert und durch zwei geteilt. Das funktioniert dann gut, wenn die Summe der beiden Werte nicht über der größten darstellbaren Zahl liegt. Wenn es einen Überlauf gibt, ist das Ergebnis falsch. Es gibt in der Informatik einige Algorithmen, die auch mit diesem Problem umgehen können, doch wir können es uns etwas einfacher machen und den jeweils höheren Datentyp nehmen. Soll z. B. von zwei int
-Variablen der Mittelwert berechnet werden, konvertieren wir die beiden int
in ein long
, addieren die Zahlen, dividieren und konvertieren zurück zum int
. Beim Datentyp long
gibt es keinen größeren primitiven Datentyp, doch lässt sich für den Fall gut der Typ BigInteger
einsetzen.
Aufgabe:
Berechne den arithmetischen Mittelwert zweier
long
-Werte, sodass es nicht zu einem Überlauf und falschen Ergebnissen kommt. Das Ergebnis soll wieder einlong
sein.
1.2.2. Zahl für Zahl über das Telefon ⭐
Durch einen neuen Deal hat Bonny Brain viel Geld verdient. Die Zahl ist so groß, dass man sie am Telefon gar nicht einfach durchsagen kann; sie muss in Häppchen übermittelt werden.
Aufgabe:
Schreibe eine neue Methode
BigInteger completeNumber(int... parts)
, die eine variable Anzahl von Zahlen bekommt und zum Schluss die große Gesamtzahl zurückgibt.
Beispiel:
completeNumber(123, 22, 989, 77, 9)
liefert einBigInteger
mit dem Wert12322989779
.
1.2.3. Klasse für Brüche entwickeln und Brüche kürzen ⭐⭐
Bonny Brain probiert ein neues Rezept für einen Rumpunsch. In der Anweisung zur Zubereitung kommen immer wieder Brüche vor wie »1/4 Liter Traubensaft« oder »1/2 Liter Rum«. Für die Feier bereitet sie 100 Portionen vor, und es entstehen Brüche wie »100/4 Liter«. Die Brüche können gekürzt werden, sodass Bonny Brain zum Beispiel weiß, dass 25 Liter Traubensaft gekauft werden müssen.
Aufgabe:
Lege eine neue Klasse
Fraction
an.Es soll einen Konstruktor
Fraction(int numerator, int denominator)
geben, der Zähler und Nenner inpublic final
Variablen ablegt.Überlege, ob es fehlerhafte Parameterbelegungen geben kann, die wir durch Ausnahmen melden sollten.
Jeder angelegte Bruch soll automatisch gekürzt werden. Greife dafür auf die Methode
gcd(…)
vonBigInteger
zurück, die den größten gemeinsamen Teiler (ggT) (engl. greatest common divisor) berechnet. Zur Erinnerung: Der ggT von Zähler und Nenner ist die größte Zahl, durch die beide teilbar sind. Man kann den Bruch vollständig kürzen, indem man sowohl Zähler als auch Nenner durch diese Zahl teilt.Zähler und Nenner können negativ sein, aber dann kann man das Vorzeichen umdrehen, sodass beide Werte positiv werden.
Die Objekte sollen alle immutable sein, und daher können die Variablen
public
sein, da sie nach dem Initialisieren über den Konstruktor nicht mehr verändert werden sollen. Anders gesagt: Die KlasseFraction
enthält keine Setter.
Ergänze den Konstruktor
Fraction(int value)
, bei dem der Nenner automatisch zu1
wird.Implementiere eine Methode, mit denen man Brüche multiplizieren kann und die Überläufe erkennt.
Implementiere eine Methode
reciprocal()
, die den Kehrbruch eines Bruches liefert, also Zähler und Nenner miteinander vertauscht. Mithilfe dieser Methode lässt sich die Division von Brüchen umsetzen.Fraction
solljava.lang.Number
erweitern und alle vorgeschriebenen Methoden implementieren.Fraction
sollComparable
implementieren, denn Brüche lassen sich in eine Dezimalzahl umrechnen, und Dezimalzahlen haben eine natürliche Ordnung.Fraction
sollequals(…)
undhashCode()
korrekt implementieren.Implementiere eine
toString()
-Methode, die eine möglichst schlanke Rückgabe liefert.
Fraction
Noch mehr Aufgaben findest du im Buch: ›Captain CiaoCiao erobert Java: Das Trainingsbuch für besseres Java. 300 Java-Workshops, Aufgaben und Übungen mit kommentierten Lösungen‹