Galileo Computing < openbook >
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel (2. Aufl.) von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Java ist auch eine Insel (2. Auflage)
gp Kapitel 5 Mathematisches
  gp 5.1 Arithmetik in Java
  gp 5.2 Die Funktionen der Math-Klasse
    gp 5.2.1 Attribute
    gp 5.2.2 Winkelfunktionen (trigonometrische Funktionen und Arcus–Funktionen)
    gp 5.2.3 Runden von Werten
    gp 5.2.4 Exponentialfunktionen
    gp 5.2.5 Division
    gp 5.2.6 Absolutwerte und Maximum, Minimum
    gp 5.2.7 Zufallszahlen
  gp 5.3 Mathe bitte strikt
    gp 5.3.1 Strikt Fließkomma mit strictfp
    gp 5.3.2 Die Klassen Math und StrictMath
  gp 5.4 Die Random-Klasse
  gp 5.5 Große Zahlen
    gp 5.5.1 Die Klasse BigInteger
    gp 5.5.2 Ganz lange Fakultäten
  gp 5.6 Probleme mit Java und der Mathematik
  gp 5.7 Das Java-Matrix-Paket Jama

Kapitel 5 Mathematisches

Vieles hätte ich verstanden, wenn man es mir nicht erklärt hätte.
– Stanislaw Jerzy Lec

5.1 Arithmetik in Java

Zahlen mit Komma nennen sich Gleitkomma-, Fließkomma-, Fließpunkt- oder Bruchzahlen. Der Begriff »Gleitkommazahl« kommt daher, dass die Zahl durch das Gleiten (Verschieben) des Dezimalpunkts als Produkt aus einer Zahl und einer Potenz der Zahl 10 dargestellt wird (also 1,23 = 123 * 10-2  ).

Java unterstützt für Fließkommazahlen die Typen float und double, die sich nach der Spezifikation IEEE 754 richten. Ein float hat die Länge von 32 Bit und ein double die Länge von 64 Bit. Die Rechenoperationen sind ebenso im IEEE-Standard »Binary and Floating-Point Arithmetic« definiert. Neben den unterschiedlichen Größen für double und float definiert das IEEE aber noch positive und negative Zahlen sowie auch eine positive oder negative Null, positives und negatives Unendlich (engl. infinity) und mehrere Zahlen, die eigentlich gar keine sind. Es handelt sich hierbei um NaN, die Abkürzung für Not-A-Number. Sie werden als Fehlerindikator für das Ergebnis von undefinierten Rechenoperationen benutzt, etwa 0/0. NaN ist als Konstante (zum Beispiel public static final double NaN = 0.0/0.0) in den Klassen Double und Float eingefügt.

Außer für den Wert NaN ist auf allen Fließkommazahlen eine totale Ordnung definiert, das heißt, sie lassen sich von der kleinsten Zahl bis zur größten aufzählen. Am Rand steht die negative Unendlichkeit, dann die negativen Zahlen, negative Null, positive Null, positive Zahlen und positives Unendlich. Die positive Null (+0.0) und die negative Null (-0.0) werden nicht unterschieden und sind gleich (0.0==-0.0). So ist auch 0.0 > -0.0 falsch. Dennoch gibt es einen kleinen Unterschied, den wir durch die Rechnung 1.0/-0.0 und 1.0/0.0 leicht sehen. Denn durch den Grenzwert geht das Ergebnis einmal gegen negativ unendlich und einmal gegen positiv unendlich.

Bleibt nur noch die einzige unsortierte Zahl NaN. Alle numerischen Vergleiche <, <=, >, >= mit NaN liefern false. Der Vergleich mit == ist false, wenn einer der Operatoren NaN ist. != verhält sich umgekehrt, ist also true, wenn einer der Operatoren NaN ist.

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 NaNNaN 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 ein int 0 oder long 0 ergibt. Leider gab es in den ersten Versionen der JVM einen Fehler, sodass Long.MAX_VALUE an Stelle von 0.0 produziert wurde. Dieser Fehler ist aber inzwischen behoben.

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 1/2 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 -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 0^0 als undefiniert behandelt. Es macht aber durchaus Sinn, 0^0 als 1 zu definieren, da es andernfalls viele Sonderbehandlungen für 0 geben müsste. Hier schreiben die Autoren R. Graham, D. Knuth, O. Patashnik des Buchs Concrete Mathematics ():

»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.«

Java-Sondertypen im Beispiel

Wir wollen in ein paar Beispielen ein negatives/positives Unendlich und Null sowie ein NaN erzeugen:

Berechnung Deutung Ergebnis String-Repräsentation
1E300 * 1E20 Positiv unendlich Überlauf Infinity
-1E300 * 1E20 Negativ unendlich Überlauf -Infinity
1E-322 * 0.0001 Positive 0 Unterlauf 0.0
1E-322 * 0.0001 Negative 0 Unterlauf -0.0
0.0/0.0 Not a Number Undefiniert NaN

Das Minimum für double-Werte liegt bei etwa 10^-324 und das Maximum bei etwa 10^308.

Hinweis   Die Anzeige des Über-/Unterlaufs und des undefinierten Ergebnisses gibt es nur bei Fließkommazahlen, nicht aber bei Ganzzahlen.


Galileo Computing

5.1.1 Soll eine Division durch Null zur Übersetzungszeit erkannt werden?  toptop

Ein interessantes Phänomen in Java, das von Compilern ganz unterschiedlich behandelt wird, ist die Division durch Null. Die erste Frage ist, ob die Division zur Laufzeit erkannt und gemeldet werden soll oder nicht. Die zweite ist, wie sich der Compiler verhalten soll, wenn eine Division durch Null erkannt wird, diese aber zu einem Stück Code gehört, das nie ausgeführt wird.

Listing 5.1   DivNull.java

class DivNull
{
  int a = 1 / 0;

  int b = false ? 1 / 0 : 5;

  boolean b1 = false && 1 / 0 == 1;

  boolean b2 = true || 1 / 0 == 1;
}

Der Compiler von Sun, javac, meldet keinen Fehler. Der Jikes-Compiler allerdings meldet in allen Fällen eine

Attempt to divide by zero.

Die Sprachspezifikation von Java äußert zwar mehr oder weniger präzise, welche Ausdrücke konstant ausgewertet werden können, gibt jedoch nicht an, was bei illegalen Ausdrücken geschehen soll. Denn bis auf die Zuweisung an die Variable a würde das Programm korrekt ausgeführt werden können. Sprich: Der Compiler beschwert sich über Programmkonstrukte, die zur Laufzeit keine Probleme bereiten würden.






1   Dass diese Frage besonders in den siebziger Jahren interessant war, zeigen schon die Aufsätze von H. E. Vaughan, The expression ’0^0’, Mathematics Teacher 63 (1970) und von Louis M. Rotando & Henry Korn, The Indeterminate Form 0^0, Mathematics Magazine, Vol. 50, No. 1 (January 1977). Auch L. J. Paige, A note on indeterminate forms, American Mathematical Monthly, 61 (1954), 189-190, scheint hier interessante Einblicke zu geben.





Copyright © Galileo Press GmbH 2003
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de