Begrenzungen von Operatorrechnungen mit Java
Bei Rechnungen mit Zahlen kommt es in Java immer wieder zu Abweichungen von den schulmathematisch richtigen Ergebnissen . Java -Implementationen rechnen also tatsächlich manchmal mehr oder weniger „falsch“! – jedenfalls verglichen mit den Erwartungen eines Lernenden. Tatsächlich richten sich Java -Implementationen aber oft nach der Norm IEEE 754, nach welcher die Ergebnisse dann korrekt sind.
Begrenzungen beim Datentyp »int«
Das folgende Programmbeispiel zeigt, wie bei Rechnungen mit int-Werten falsche Ergebnisse entstehen können. Dies kann vermieden werden, indem sichergestellt wird, daß kein Wert einer Rechnung zu groß (größer als zirka eine Milliarde) wird.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1000000 * 1000000 ); }}java.lang.System.out
-727379968
Das folgende Programmbeispiel zeigt, wie ein Rechenergebnis „fast“ richtig zu sein scheint – abgesehen vom falschen Vorzeichen!
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( ( 2_147_483_647 + 1 ) ); }}java.lang.System.out
-2147483648
In dem folgenden Programm wollte vielleicht jemand das Vorzeichen korrigieren, indem er ein Minuszeichen hinzufügt. Allerdings ändert dies die Ausgabe des Programms überhaupt nicht!
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( -( 2_147_483_647 + 1 ) ); }}java.lang.System.out
-2147483648
In vielen Fälle kann man aber mit »int« korrekt rechnen, solange die dabei vorkommenden Werte nicht zu groß werden.
Begrenzungen beim Datentyp »double«
Nun könnte man denken, daß Rechenprobleme vermieden werden können, indem immer der Datentyp »double« verwendet wird, aber auch hier zeigen sich Rechenfehler. Man darf den hinteren Stellen eines double-Wertes nicht trauen! In der Regel kann man aber davon ausgehen, daß bei einfachen Rechnungen mit dem Typ »double« die ersten 10 Stellen stimmen, was für viele technische Berechnungen ausreichend genau ist.
Interne Näherungswerte
Der interne Wert des Literals »0.1« beträgt genau «0.1000000000000000055511151231257827021181583404541015625», da die verwendete Codierung den Wert ‹ 0,1 › nicht genau darstellen kann. Die Ausgabedarstellung von «0.1000000000000000055511151231257827021181583404541015625» ist aber wieder »0.1«, so daß die kleine Abweichung nicht beobachtbar ist.
- Ausgabe des Wertes eines Ausdrucks
Auswertung Ausgabe
Ausdruck -----------------------> Wert -----------------------> Textdarstellung0.1 0.1000000000000000055511151231257827021181583404541015625 0.1
(0x1.999999999999ap-4)
Der Wert in den Klammern gibt die interne binäre Darstellung durch Ziffern, die jeweils vier Bits zusammenfassen, an. Er wäre genau gleich ‹ 0,1 ›, wenn Platz für unendlich viele 9-Bitgruppen wäre.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 0.1 ); }}- Protokoll
0.1
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 0.1000000000000000055511151231257827021181583404541015625 ); }}- Protokoll
0.1
Ergebnisse von Rechnungen
Bei Rechnungen können sich Fehler von Operandenwerten so addieren, daß der Gesamtfehler auch in der Ausgabe des Ergebnisses sichtbar wird.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 0.1 + 0.2 ); }}java.lang.System.out
0.30000000000000004
Das folgende Programmbeispiel zeigt, daß bei »double« nicht nur Werte hinter dem Komma, sondern auch schon Stellen vor dem Komma nicht mehr unbedingt mathematisch korrekt sein müssen.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 10000000000000000. + 1.
- 10000000000000000. ); }}java.lang.System.out
0.0
Das folgende Programmbeispiel zeigt den größten double-Wert, der noch vom um »1.« erhöhten Wert unterschieden werden kann. Das Ergebnis der Rechnung ist noch mathematisch korrekt.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 9_007_199_254_740_991. + 1. - 9_007_199_254_740_991. - 1. ); }}transcript
0.0
In dem folgenden Programm wird der Nachfolger des Wertes aus dem vorherigen Programm verwendet. Bei diesem Ändert die Addition von »1.« den Wert nicht mehr. Das Ergebnis der Rechnung ist nun nicht mehr mathematisch korrekt.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 9_007_199_254_740_992. + 1. - 9_007_199_254_740_992. - 1. ); }}transcript
-1.0
Die Umstände, unter denen es zu solchen „Rechenfehlern“ kommen kann, müssen jedem ernsthaften Programmierer bekannt sein. Es wäre an dieser Stelle des Kurses aber noch zu früh, dieses Thema der möglichen Abweichungen vom mathematisch korrekten Ergebnis ausführlich zu behandeln. Vorläufig sollte man darauf achten, daß die Zahlen und die (Zwischen-)Ergebnisse bei Verwendung des Datentyps »int« nicht zu groß werden (nicht größer als zwei Milliarden und nicht kleiner als minus zwei Milliarden) und wissen, daß Ergebnisse vom Typ »double« in den hinteren Stellen (ungefähr nach der 10. Stelle) vom korrekten Ergebnis abweichen können.
Ein Kuriosität stellt es ferner dar, daß das folgende Programm übersetzt wird, aber nicht der danach stehende Quelltext. Dies liegt daran, daß es für die Folge aus den beiden lexikalischen Einheiten »-« und »2147483648« ein spezielle Regel gibt, die sie erlaubt und ihr eine Bedeutung gibt, während das Literal »2147483648« ansonsten nicht erlaubt ist.
Main.java
public final class Main
{ public static void main( final java.lang.String args[] )
{ java.lang.System.out.println( ( -2147483648 )); }}transcript
-2147483648
Main.txt
public final class Main
{ public static void main( final java.lang.String args[] )
{ java.lang.System.out.println( -( 2147483648 )); }}transcript
Main.txt:3: error: integer number too large: 2147483648
{ java.lang.System.out.println( -( 2147483648 )); }}
^
1 error