21.3 Die Eigenschaften der Klasse Math
Die Klasse java.lang.Math ist eine typische Utility-Klasse, die nur statische Methoden (bzw. Attribute als Konstanten) deklariert. Mit dem privaten Konstruktor lassen sich keine Exemplare von Math erzeugen.
21.3.1 Attribute
Die Math-Klasse besitzt zwei statische Attribute:
class java.lang.Math
static final double E
die eulersche Zahl[ 272 ](Die irrationale Zahl e ist nach dem schweizerischen Mathematiker Leonhard Euler (1707–1783) benannt. ) e = 2,7182818284590452354static final double PI
die Kreiszahl Pi[ 273 ](Wer noch auf der Suche nach einer völlig unsinnigen Information ist: Die einmilliardste Stelle hinter dem Komma von Pi ist eine Neun. ) = 3,14159265358979323846
21.3.2 Absolutwerte und Vorzeichen
Die zwei statischen abs(…)-Methoden liefern den Betrag des Arguments (mathematische Betragsfunktion: y = |x|). Sollte ein negativer Wert als Argument übergeben werden, wandelt abs(…) ihn in einen positiven Wert um.
Eine spezielle Methode ist auch copySign(double, double). Sie ermittelt das Vorzeichen einer Fließkommazahl und überträgt es auf eine andere Zahl.
class java.lang.Math
static int abs(int x)
static long abs(long x)
static float abs(float x)
static double abs(double x)
static double copySign(double magnitude, double sign)
static float copySign(float magnitude, float sign)
Liefern magnitude als Rückgabe, aber mit dem Vorzeichen von sign.
[»] Hinweis
Es gibt genau einen Wert, auf den Math.abs(int) keine positive Rückgabe liefern kann: –2.147.483.648. Dies ist die kleinste darstellbare int-Zahl (Integer.MIN_VALUE), während +2.147.483.648 gar nicht in ein int passt! Die größte darstellbare int-Zahl ist 2.147.483.647 (Integer.MAX_VALUE). Was sollte abs(-2147483648) auch ergeben?
Vorzeichen erfragen
Die statische Methode signum(value) liefert eine numerische Rückgabe für das Vorzeichen von value, und zwar »+1« für positive, »–1« für negative Zahlen und »0« für 0. Die Methode ist nicht ganz logisch auf die Klassen Math für Fließkommazahlen und Integer/Long für Ganzzahlen verteilt:
java.lang.Integer.signum(int i)
java.lang.Long.signum(long i)
java.lang.Math.signum(double d)
java.lang.Math.signum(float f)
21.3.3 Maximum/Minimum
Die statischen max(…)-Methoden liefern den größeren der übergebenen Werte. Die statischen min(…)-Methoden liefern den kleineren von zwei Werten als Rückgabewert.
class java.lang.Math
static int max(int x, int y)
static long max(long x, long y)
static float max( float x, float y )
static double max(double x, double y)
static int min(int x, int y)
static long min(long x, long y)
static float min(float x, float y)
static double min(double x, double y)
Als Vararg sind diese Methoden nicht deklariert, auch gibt es keine Min-Max-Methoden für drei Argumente. Wenn Arrays vorliegen, muss eine andere Lösung gewählt werden, etwa durch Arrays.stream(value).min().getAsInt().
21.3.4 Runden von Werten
Für die Rundung von Werten bietet die Klasse Math fünf statische Methoden:
class java.lang.Math
static double ceil(double a)
static double floor(double a)
static int round(float a)
static long round(double a)
static double rint(double a)
Auf- und Abrunden mit ceil(double) und floor(double)
Die statische Methode ceil(double) dient dem Aufrunden und liefert die nächsthöhere Ganzzahl (jedoch als double, nicht als long), wenn die Zahl nicht schon eine ganze Zahl ist. Die statische Methode floor(double) rundet auf die nächstniedrigere Ganzzahl ab:
System.out.println( Math.ceil(-99.1) ); // -99.0
System.out.println( Math.floor(-99.1) ); // -100.0
System.out.println( Math.ceil(-99) ); // -99.0
System.out.println( Math.floor(-99) ); // -99.0
System.out.println( Math.ceil(-.5) ); // -0.0
System.out.println( Math.floor(-.5) ); // -1.0
System.out.println( Math.ceil(-.01) ); // -0.0
System.out.println( Math.floor(-.01) ); // -1.0
System.out.println( Math.ceil(0.1) ); // 1.0
System.out.println( Math.floor(0.1) ); // 0.0
System.out.println( Math.ceil(.5) ); // 1.0
System.out.println( Math.floor(.5) ); // 0.0
System.out.println( Math.ceil(99) ); // 99.0
System.out.println( Math.floor(99) ); // 99.0
Die genannten statischen Methoden haben auf ganze Zahlen keine Auswirkung.
Kaufmännisches Runden mit round(…)
Die statischen Methoden round(double) und round(float) runden kaufmännisch auf die nächste Ganzzahl vom Typ long bzw. int. Ganze Zahlen werden nicht aufgerundet. Wir können round(…) als Gegenstück zur Typumwandlung (long) doublevalue einsetzen:
System.out.println( Math.round(1.01) ); // 1
System.out.println( Math.round(1.4) ); // 1
System.out.println( Math.round(1.5) ); // 2
System.out.println( Math.round(1.6) ); // 2
System.out.println( (int) 1.6 ); // 1
System.out.println( Math.round(30) ); // 30
System.out.println( Math.round(-2.1) ); // -2
System.out.println( Math.round(-2.9) ); // -3
System.out.println( (int) -2.9 ); // -2
[»] Interna
Die Methoden Math.round(int) und Math.round(long) sind in Java ausprogrammiert. Sie addieren zum aktuellen Parameter 0,5 und übergeben das Ergebnis der statischen floor( double)-Methode.
public static long round( double a ) {
return (long) floor( a + 0.5 );
}
Gerechtes Runden rint(double)
rint(double) ist mit round(…) vergleichbar, nur ist es im Gegensatz zu round(…) gerecht, was bedeutet, dass rint(double) bei 0,5 in Abhängigkeit davon, ob die benachbarte Zahl ungerade oder gerade ist, auf- oder abrundet:
System.out.println( Math.round(-1.5) ); // -1
System.out.println( Math.rint( -1.5) ); // -2.0
System.out.println( Math.round(-2.5) ); // -2
System.out.println( Math.rint( -2.5) ); // -2.0
System.out.println( Math.round( 1.5) ); // 2
System.out.println( Math.rint( 1.5) ); // 2.0
System.out.println( Math.round( 2.5) ); // 3
System.out.println( Math.rint( 2.5) ); // 2.0
Mit einem konsequenten Auf- oder Abrunden pflanzen sich natürlich auch Fehler ungeschickter fort als mit dieser 50/50-Strategie.
[zB] Beispiel
Die statische rint(double)-Methode lässt sich auch einsetzen, wenn Zahlen auf zwei Nachkommastellen gerundet werden sollen. Ist d vom Typ double, so ergibt der Ausdruck Math. rint(d*100.0)/100.0 die gerundete Zahl.
class Round2Scales {
public static double roundScale2( double d ) {
return Math.rint( d * 100 ) / 100.;
}
public static void main( String[] args ) {
System.out.println( roundScale2(+1.341 ) ); // 1.34
System.out.println( roundScale2(-1.341 ) ); // -1.34
System.out.println( roundScale2(+1.345 ) ); // 1.34
System.out.println( roundScale2(-1.345 ) ); // -1.34
System.out.println( roundScale2(+1.347 ) ); // 1.35
System.out.println( roundScale2(-1.347 ) ); // -1.35
}
}
Arbeiten wir statt mit rint(double) mit round(…), wird die Zahl 1,345 nicht auf 1,34, sondern auf 1,35 gerundet. Wer nun Lust hat, etwas auszuprobieren, darf testen, wie der Format-String %.2f bei printf(…) rundet.
21.3.5 Rest der ganzzahligen Division *
Neben dem Restwert-Operator %, der den Rest der Division berechnet, gibt es auch eine statische Methode Math.IEEEremainder(double, double).
double a = 44.0;
double b = 2.2;
System.out.println( a / b ); // 20.0
System.out.println( a % b ); // 2.1999999999999966
System.out.println( Math.IEEEremainder( a, b ) ); // -3.552713678800501E-15
Das zweite Ergebnis ist mit der mathematischen Ungenauigkeit fast 2,2, aber etwas kleiner, sodass der Algorithmus nicht noch einmal 2,2 abziehen konnte. Die statische Methode IEEEremainder(double, double) liefert ein Ergebnis nahe null (–0,000000000000003552713 6788005), was besser ist, denn 44,0 lässt sich ohne Rest durch 2,2 teilen, also wäre der Rest eigentlich 0.
class java.lang.Math
static double IEEEremainder(double dividend, double divisor)
Liefert den Rest der Division von Dividend und Divisor, so wie es der IEEE-754-Standard vorschreibt.
Eine eigene statische Methode, die mitunter bessere Ergebnisse liefert – mit den Werten 44 und 2,2 wirklich 0,0 –, ist die folgende:
public static double remainder( double a, double b ) {
return Math.signum(a) *
(Math.abs(a) - Math.abs(b) * Math.floor(Math.abs(a)/Math.abs(b)));
}
21.3.6 Division mit Rundung in Richtung negativ unendlich, alternativer Restwert *
Die Ganzzahldivision in Java ist simpel gestrickt. Vereinfacht ausgedrückt: Konvertiere die Ganzzahlen in Fließkommazahlen, führe die Division durch, und schneide alles hinter dem Komma ab. So ergeben zum Beispiel 3 / 2 = 1 und 9 / 2 = 4. Bei negativem Ergebnis, das entweder durch einen negativen Dividenden oder durch einen negativen Divisor zustande kam, folgt das gleiche Spiel: -9 / 2 = -4 und 9 / -2 = -4. Schauen wir uns einmal die Rundungen an.
Ist das Ergebnis einer Division positiv und mit Nachkommaanteil, so wird das Ergebnis durch das Abschneiden der Nachkommastellen ein wenig kleiner, also abgerundet. Wäre 3 / 2 bei Fließkommazahlen 1,5, ist es bei einer Ganzzahldivision abgerundet 1. Bei negativen Ergebnissen einer Division ist das genau andersherum, denn durch das Abschneiden der Nachkommastellen wird die Zahl etwas größer. -3 / 2 ist genau genommen –1,5, aber bei der Ganzzahldivision -1. Doch -1 ist größer als –1,5. Java wendet ein Verfahren an, das gegen null rundet.
Die gegen minus unendlich rundende Methode floorDiv(…)
Drei Methoden runden bei negativem Ergebnis einer Division nicht gegen null, sondern gegen negativ unendlich, also auch in Richtung der kleineren Zahl, wie es bei den positiven Ergebnissen ist.
class java.lang.Math
class java.lang.StrictMath
static long floorDiv(long x, long y)
static long floorDiv(long x, int y) – erst seit Java 9
Ganz praktisch heißt das: 4/2 = Math.floorDiv(4, 3) = 1, aber wo -4 / 3 = -1 ergibt, liefert Math.floorDiv(-4, 3) = -2.
Die gegen minus unendlich rundende Methode floorMod(…)
Die Division taucht indirekt auch bei der Berechnung des Restwerts auf. Zur Erinnerung: Der Zusammenhang zwischen Division a/b und Restwert a%b (a heißt Dividend, b Divisor) ist (int)(a/b) * b + (a%b) = a. In der Gleichung gibt es eine Division, doch da es mit a / b und floorDiv(a, b) zwei Arten von Divisionen gibt, muss es folglich auch zwei Arten von Restwertbildung geben, die sich dann unterscheiden, wenn die Vorzeichen unterschiedlich sind. Neben a % b gibt es daher die Bibliotheksmethode floorMod(a, b), und der Zusammenhang zwischen floorMod(…) und floorDiv(…) ist: floorDiv(a, b) * b + floorMod(a, b) == b. Nach einer Umformung der Gleichung folgt floorMod(a, b) = a - (floorDiv(a, b) * b). Das Ergebnis ist im Bereich -abs(b) und abs(b), und das Vorzeichen des Ergebnisses bestimmt der Divisor b (beim %-Operator ist es der Dividend a).
Die Javadoc zeigt ein Beispiel mit den Werten 4 und 3 bei unterschiedlichen Vorzeichen:
floorMod(…)-Methode | %-Operator |
---|---|
floorMod(+4, +3) == +1 | +4 % +3 == +1 |
floorMod(+4, -3) == -2 | +4 % -3 == +1 |
floorMod(-4, +3) == +2 | -4 % +3 == -1 |
floorMod(-4, -3) == -1 | -4 % -3 == -1 |
Sind die Vorzeichen für a und b identisch, so ist auch das Ergebnis von floorMod(a, b) und a % b gleich. Die Beispiele in der Tabelle machen auch den Unterschied im Ergebnisvorzeichen deutlich, das einmal vom Dividenden (%) und einmal vom Divisor (floorMod(…)) kommt. Die komplett anderen Ereignisse (vom Vorzeichen einmal abgesehen) bei den Paaren (+4, –3) und (–4, +3) ergeben sich ganz einfach aus unterschiedlichen Ergebnissen der Division:
floorMod(+4, -3) = +4 - (floorDiv(+4, -3) * -3) = +4 - (-2 * -3) = +4 - +6 =
-2 +4 % -3 = +4 - (+4/-3 * -3) = +4 – (-1 * -3) = +4 – 3 = +1
static int floorMod(int x, int y)
static long floorMod(long x, long y)
static long floorMod(long x, int y) – erst seit Java 9
Die Implementierung ist relativ einfach mit x - floorDiv(x, y) * y angegeben.
21.3.7 Multiply-Accumulate
Die mathematische Operation a × b + c kommt in Berechnungen oft vor, sodass Prozessoren heute diese Berechung optimiert – das heißt schneller und mit besserer Genauigkeit – durchführen können. Bei Math und StrictMath gibt es seit Java 9 die Methode fma(…) für die fused multiply-accumulate-Funktion aus dem IEEE-754-Standard.
class java.lang.Math
class java.lang.StrictMath
static double fma(double a, double b, double c)
static float fma(float a, float b, float c)
Führen a × b + c mit dem RoundingMode.HALF_EVEN durch. Gegenüber einem einfachen a * b + c berücksichtigt fma(…) die Besonderheiten unendlich und NaN.
21.3.8 Wurzel- und Exponentialmethoden
Die Math-Klasse bietet außerdem Methoden zum Berechnen der Wurzel und weitere Exponentialmethoden:
class java.lang.Math
static double sqrt(double x)
Liefert die Quadratwurzel von x; sqrt steht für square root.static double cbrt(double a)
Berechnet die Kubikwurzel aus a.static double hypot(double x, double y)
Berechnet die Wurzel aus x2 + y2, also den euklidischen Abstand. Könnte als sqrt(x*x, y*y) umgeschrieben werden, doch hypot(…) bietet eine bessere Genauigkeit und Performance.static double scalb(double d, double scaleFactor)
Liefert d mal 2 hoch scaleFactor. Kann prinzipiell auch als d * Math.pow(2, scaleFactor) geschrieben werden, doch scalb(…) bietet eine bessere Performance.static double exp(double x)
Liefert den Exponentialwert von x zur Basis e (der eulerschen Zahl e = 2,71828…), also ex.static double expm1(double x)
Liefert den Exponentialwert von x zur Basis e minus 1, also ex – 1. Berechnungen nahe null kann expm1(x) + 1 präziser ausdrücken als exp(x).static double pow(double x, double y)
Liefert den Wert der Potenz xy. Für ganzzahlige Werte gibt es keine eigene Methode.
Die Frage nach dem 0.0/0.0 und 0.0^0.0 *
Wie wir wissen, ist 0.0/0.0 ein glattes NaN. Im Unterschied zu den Ganzzahlwerten bekommen wir hier allerdings keine Exception, denn dafür ist extra die Spezialzahl NaN eingeführt worden. Interessant ist die Frage, was denn (long)(double)(0.0/0.0) ergibt. Die Sprachdefinition sagt hier in § 5.1.3, dass die Konvertierung eines Fließkommawerts NaN in ein int 0 oder in ein long 0 ergibt.
Eine weitere spannende Frage ist das Ergebnis von 0.00.0. Um allgemeine Potenzen zu berechnen, wird die statische Funktion Math.pow(double a, double b) eingesetzt. Wir erinnern uns aus der Schulzeit daran, dass wir die Quadratwurzel einer Zahl ziehen, wenn der Exponent b genau ½ ist. Doch jetzt wollen wir wissen, was denn gilt, wenn a = b = 0 gilt. § 20.11.13 der Sprachdefinition fordert, dass das Ergebnis immer 1.0 ist, wenn der Exponent b gleich –0.0 oder 0.0 ist. Es kommt also in diesem Fall überhaupt nicht auf die Basis a an. In einigen Algebra-Büchern wird 00 als undefiniert behandelt. Es macht aber durchaus Sinn, 00 als 1 zu definieren, da es andernfalls viele Sonderbehandlungen für 0 geben müsste.[ 274 ](Hier schreiben R. Graham, D. Knuth und O. Patashnik, die Autoren des Buchs Concrete Mathematics (Addison-Wesley, 1994, ISBN 0-201-55802-5): »Some textbooks leave the quantity 0^0 undefined, because the functions x^0 and 0^x have different limiting values when x decreases to 0. But this is a mistake. We must define x^0 = 1 for all x, if the binomial theorem is to be valid when x=0, y=0, and/or x=-y. The theorem is too important to be arbitrarily restricted! By contrast, the function 0^x is quite unimportant.« )
21.3.9 Der Logarithmus *
Der Logarithmus ist die Umkehrfunktion der Exponentialfunktion. Die Exponentialfunktion und der Logarithmus hängen durch folgende Beziehung zusammen: Ist y = ax, dann ist x = loga(y). Der Logarithmus, den Math.log(double) berechnet, ist der natürliche Logarithmus zur Basis e. In der Mathematik wird dieser mit ln angegeben (logarithmus naturalis). Logarithmen mit der Basis 10 heißen dekadische oder briggsche Logarithmen und werden mit lg abgekürzt, und der Logarithmus zur Basis 2 (binärer Logarithmus, dualer Logarithmus) wird mit lb abgekürzt. In Java gibt es die statische Methode log10(double) für den briggschen Logarithmus lg, nicht aber für den binären Logarithmus lb, der weiterhin nachgebildet werden muss. Allgemein gilt folgende Umrechnung: logb(x) = loga(x) ÷ loga(b).
[zB] Beispiel
Eine eigene statische Methode soll den Logarithmus zur Basis 2 berechnen:
public static double lb( double x ) {
return Math.log( x ) / Math.log( 2.0 );
}
Da Math.log(2) konstant ist, sollte dieser Wert aus Performance-Gründen in einer Konstanten gehalten werden: static final double log2 = Math.log(2.0). Das gilt übrigens für andere konstante Ergebnisse auch, denn es ist nicht davon auszugehen, dass die JVM das Ergebnis des Methodenaufrufs einsetzt, anstatt die Methode aufzurufen. Für die JVM handelt es sich erst einmal um irgendeine Methode ohne Bedeutung. C-Compiler sind hier deutlich weiter.[ 275 ](Wer Lust hat, zu spielen, besucht http://godbolt.org/ und setzt im Editor Folgendes ein:
#include <math.h>
#include <stdio.h>
int main() { return (int)(log(2.0)*1000 ); })
class java.lang.Math
static double log(double a)
Berechnet von a den Logarithmus zur Basis e.static double log10(double a)
Liefert von a den Logarithmus zur Basis 10.static double log1p(double x)
Liefert log(x) + 1.
21.3.10 Winkelmethoden *
Die Math-Klasse stellt einige winkelbezogene Methoden und deren Umkehrungen zur Verfügung. Im Gegensatz zur bekannten Schulmathematik werden die Winkel für sin(double), cos(double) und tan(double) im Bogenmaß (2π entspricht einem Vollkreis) und nicht im Gradmaß (360 Grad entspricht einem Vollkreis) übergeben.
class java.lang.Math
static double sin(double x)
static double cos(double x)
static double tan(double x)
Liefern den Sinus, Kosinus bzw. Tangens von x.
Arcus-Methoden
Die Arcus-Methoden realisieren die Umkehrfunktionen zu den trigonometrischen Methoden. Das Argument ist kein Winkel, sondern zum Beispiel bei asin(double) der Sinuswert zwischen –1 und 1. Das Ergebnis ist ein Winkel im Bogenmaß, etwa zwischen –π/2 und π/2.
class java.lang.Math
static double asin(double x)
static double acos(double x)
static double atan(double x)
Liefern den Arcus-Sinus, Arcus-Kosinus bzw. Arcus-Tangens von x.static double atan2(double x, double y)
Liefert bei der Konvertierung von Rechteckkoordinaten in Polarkoordinaten den Winkel theta, also eine Komponente des Polarkoordinaten-Tupels. Die statische Methode berücksichtigt das Vorzeichen der Parameter x und y, und der freie Schenkel des Winkels befindet sich im richtigen Quadranten.
Hyperbolicus-Methoden bietet Java über sinh(double), tanh(double) und cosh(double).
Umrechnungen von Gradmaß in Bogenmaß
Zur Umwandlung eines Winkels vom Gradmaß ins Bogenmaß und umgekehrt existieren zwei statische Methoden:
class java.lang.Math
static double toRadians(double angdeg)
Wandelt einen Winkel vom Gradmaß ins Bogenmaß um.static double toDegrees(double angrad)
Wandelt einen Winkel vom Bogenmaß ins Gradmaß um.
21.3.11 Zufallszahlen
Positive Gleitkomma-Zufallszahlen zwischen größer gleich 0,0 und echt kleiner 1,0 liefert die statische Methode Math.random(). Die Rückgabe ist double, und eine Typumwandlung in int führt immer zum Ergebnis 0.
Möchten wir Werte in einem anderen Wertebereich haben, ist es eine einfache Lösung, die Zufallszahlen von Math.random() durch Multiplikation (Skalierung) auf den gewünschten Wertebereich auszudehnen und per Addition (ein Offset) geeignet zu verschieben. Um ganzzahlige Zufallszahlen zwischen min (inklusiv) und max (inklusiv) zu erhalten, schreiben wir:
public static long random( long min, long max ) {
return min + Math.round( Math.random() * (max - min) );
}
Eine Alternative bietet der direkte Einsatz der Klasse Random und der Objektmethode nextInt(n).
Die Implementierung steckt nicht in der Mathe-Klasse selbst, sondern in Random, die wir uns in Abschnitt 21.5, »Zufallszahlen: Random, SecureRandom und SplittableRandom«, genauer ansehen.