Gleitkommaarithmetik in Java (Gleitkommaarithmetik in Java), Lektion, Seite 723092
https://www.purl.org/stefan_ram/pub/gleitkommaarithmetik_java (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram
Java-Kurs

Gleitkommaarithmetik in Java 

Spezielle Werte

Bei Gleitkommazahlen werden die Regeln von IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-1985 (IEEE, New York)  zugrunde gelegt. Die Gleitkommaarithmetik nach IEEE 754  kennt einige spezielle Werte, die es so nicht unbedingt in den Zahlen der Schulmathematik gibt.

»-0.0« – Die negative Null

Das Negative von »0.0« ist in Java  nicht einfach wieder »0.0«, sondern »-0.0«: Der Datentyp »double« kennt eine negative Null, die es in der Mathematik nicht gibt!

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 0.0 ); }}
Protokoll
0.0
Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( -0.0 ); }}
Protokoll
-0.0

»Infinity« – Unendlich

Eine Division durch 0 ist in der Schulmathematik „verboten“ (nicht definiert), jedoch in Java  für double-Werte erlaubt.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1./0 ); }}
java.lang.System.out
Infinity

Das englische Wort “infinity ” bedeutet „unendlich“.

Die Division einer »1./0« ergibt den speziellen Wert »Infinity«.

In der Mathematik verwendet man solch einen Wert nicht, unter anderem weil aus ‹ 1∕0 = ∞ › und ‹ 2∕0 = ∞ › durch Multiplikation mit 0 folgen würde ‹ 1 = 0 × ∞ › und ‹ 2 = 0 × ∞ › also ‹ 1 = 2 ›.)

Auch durch eine Addition kann sich der Wert „Unendlich“ ergeben.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1E308 + 1E308 ); }}
java.lang.System.out
Infinity

»-Infinity« – minus Unendlich

Das Negative von »Infinity« ist »-Infinity«.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( -1./0 ); }}
Protokoll
-Infinity

»-Infinity« ergibt sich auch bei der Division von »1« durch die negative Null »-0.0«.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1/-0. ); }}
Protokoll
-Infinity

Das nächste Beispiel zeigt wieder die in der Mathematik nicht existierende „negative Null“, als Kehrwert von „minus Unendlich“.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1./( -1./0 )); }}
java.lang.System.out
-0.0

»NaN« – „keine Zahl“

Für bestimmte Operationen will IEEE 754  keine normale Zahl als Ergebnis festlegen.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 0./0 ); }}
java.lang.System.out
NaN

»NaN« = “not a number ” = „keine Zahl“ ist eine Art von „Metawert“ des Datentyps »double«, der die Abwesenheit eines normalen numerischen Werts kennzeichnet.

Wenn dieser Wert in Rechnungen verwendet wird, ergibt sich in der Regel wieder »NaN«.

Darstellungseffekt bei Gleitkommazahlen

Numerische Ungenauigkeit durch binäre Darstellung

Java  kann nicht immer so gut rechnen, wie es ein Mensch mit Kopfrechnen kann:

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 0.1 + 0.1 + 0.1 ); }}
java.lang.System.out
0.30000000000000004

Man kann die Subtraktion gut verwenden, um zu sehen, ob zwei Werte gleich  sind, weil im allgemeinen nur dann ihre Differenz gleich 0 ist.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( ( 0.1 + 0.1 + 0.1 )-0.3 ); }}
java.lang.System.out
5.551115123125783E-17

Wenn ein Zwischenergebnis, das eigentlich 0 sein sollte, etwas davon abweicht, dann kann solch ein Fehler durch nachfolgende Rechenoperationen vergrößert werden.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( ( ( 0.1 + 0.1 + 0.1 )- 0.3 )* 1E18 ); }}
java.lang.System.out
55.51115123125783

Die Ursache für die Abweichung von dem Wert ‹ 0,3 › ist nicht die Addition. Bereits der Wert ‹ 0,1 › kann mit dem Datentyp »double« nicht mehr genau dargestellt werden. Dies wird aber normalerweise durch eine etwas „geschönte“ Ausgabe nicht sichtbar. Nach der Addition wird die schon vorher bestehende Abweichung nur erkennbar.

Um zu verstehen, warum ‹ 0,1 › nicht als Wert des Datentyps »double« dargestellt werden kann, muß man wissen, daß die Werte dieses Typs intern im Dualsystem dargestellt werden.

‹ 0,1 › im Dualsystem (=Binärsystem, d.h., Zahlensystem zur Basis 2):
         ____
0,1 = o.oooll
= 0 × 1 + 0 × ½ + 0 × ½² + 0 × ½³ + 1 × ½^4 + 1 × ½^5 + ...
‹ 0,1 › normalisiert
   ____     ____
o.oooll = l.loolB-4
Das »B« soll oben bedeuten: „mal 2 hoch“, der Exponent ist dann wieder im Zehnersystem geschrieben.
= ( 1 × 1 + 1 × ½ + 0 × ½² + 0 × ½³ + 1 × ½^4 + ... )× 2^(-4)

Der Strich über einer Ziffergruppe kennzeichnet eine Periode, diese Ziffergruppe ist also unendlich  oft zu wiederholen. Das heißt: Im Zweiersystem hat die Zahl ‹ 0,1 › unendlich viele Nachkommastellen! Mit einem double-Wert kann das aber nicht dargestellt werden, da für die sogenannte „Mantisse“ nur 52 Bit zur Verfügung stehen. Daher kann man mit einem double-Wert nur eine Näherung an ‹ 0,1 › darstellen.

URIs zum Umrechnen ins Binärsystem

http://www.exploringbinary.com/binary-converter/

Wolfram Alpha

Ein Wert des Datentyps »double« hat ein Bit für sein Vorzeichen, 52 Bit für die sogenannte „Mantisse“ (auch „Signifikant“) genannt, also die binäre Ziffernfolge und 11 Bit (in sogenannter „Zweierkomplement-Darstellung“) für den sogenannten „Exponenten“ (die Größenordnung).

Darstellung von 0,1 durch 64 Bit ("1." ist immer fest vorgegeben):
|<---------------------- 52 ---------------------->| |<- 11 ->|
l.loolloolloolloolloolloolloolloolloolloolloolloolloloBllllllllloo
binär
1. 9 9 9 9 9 9 9 9 9 9 9 9 A -4 hexadezimal
Dieses ist zurückgewandelt ins Dezimalsystem genau gleich
0.1000000000000000055511151231257827021181583404541015625

Java  könnte double -Werte zwar auch im Dezimalsystem speichern, womit dann solche Ungenauigkeiten vermieden werden könnten, aber dann wären die Rechenoperationen langsamer, da sie dann nicht auf die Unterstützung durch den Prozessor zurückgreifen könnten. Für technische Zwecke sind Abweichungen in den hinteren Stellen nicht störend, so daß sie in Kauf genommen werden können. Für kaufmännische oder andere Zwecke, bei denen es auf ganz genaues Rechnen ankommt, gibt es in Java  auch noch Möglichkeiten, die aber etwas aufwendiger sind und daher in dieser Lektion noch nicht behandelt werden sollen.

Manchmal werden solche Abweichungen als „Rundungsfehler“ bezeichnet. Die Ursache des Effekts ist aber nicht die Rundung, sondern vorwiegend die Darstellung im Binärsystem, wie oben erklärt wurde.

Ein weiteres Beispiel für Abweichungen von der Schulmathematik:

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1 - 0.9 ); }}
java.lang.System.out
0.09999999999999998

Es folgt noch eine Abweichung vom „mathematisch korrekten“ Ergebnis bei Verwendung von Gleitkommazahlen. Die Abweichung liegt – wie schon einmal weiter oben – an der Problematik den Wert 1,1 im Dualsystem genau darzustellen.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1.1 * 1.1 ); }}
java.lang.System.out
1.2100000000000002

Die Differenz macht die Abweichung deutlich.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1.1 * 1.1 - 1.21 ); }}
java.lang.System.out
2.220446049250313E-16

Numerische Ungenauigkeit durch begrenzte Mantissengröße

Eine Abweichung vom „mathematisch korrekten“ Ergebnis.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 1e20 + 1 - 1e20 ); }}
java.lang.System.out
0.0
Auswertung
1e20 + 1 - 1e20  →
1e20 - 1e20 →
0.

In diesem Beispiel sieht man eine Ungenauigkeit von Gleitkommazahlen, die nicht mit der binären Darstellung zu tun hat (wie weiter oben), denn diese binäre Darstellung macht sich erst bei den Nachkommastellen bemerkbar und hier treten keine Nachkommastellen auf. Gleitkommazahlen dürfen vielmehr in den hinteren Stellen ungenau sein, selbst, wenn diese vor  dem Komma stehen. Um den Wert 10²°+1 genau darzustellen, wären 20 Stellen nötig: 10²°+1 = 100.000.000.000.000.000.001, Gleitkommazahlen umfassen jedoch nur zirka 15 Stellen. Daher darf die 1 am Ende einfach weggelassen werden. Für technische Berechnungen sind solche kleinen Abweichungen oft akzeptabel, für den kaufmännischen Bereich sollte aber unter Umständen nach einer anderen Lösung gesucht werden (Hinweise dazu folgen später im Kurs).

Noch eine Abweichung vom „mathematisch korrekten“ Ergebnis.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 2 - 1.9999999999999999 ); }}
java.lang.System.out
0.0

Die größte int-Zahl ist »2147483647« (‹ 2³¹−1 ›). Diese Zahl kann noch genau als double-Werte dargestellt und vom um Eins verminderten double-Werte »2147483646« (‹ 2³¹−2 ›) unterschieden werden.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 2_147_483_647. ); }}
Protokoll
2.147483647E9
Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 2_147_483_647. - 1. ); }}
Protokoll
2.147483646E9

Erst »9007199254740993.0« (‹ 2⁵³+1 ›) kann dann nicht mehr von »9007199254740992.0« (‹ 2⁵³ ›) unterschieden werden.

Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 9_007_199_254_740_992. - 9_007_199_254_740_991. ); }}
Protokoll
1.0
Main.java
public class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( 9_007_199_254_740_993. - 9_007_199_254_740_992. ); }}
Protokoll
0.0

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram723092 stefan_ram:723092 Gleitkommaarithmetik in Java Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd723092, slrprddef723092, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/gleitkommaarithmetik_java