Gleitkomma-Arithmetik

Willkommen zu einer weiteren Ausgabe von Under The Hood . Diese Kolumne soll Java-Entwicklern einen Einblick in die verborgene Schönheit ihrer laufenden Java-Programme geben. In der Spalte dieses Monats wird die im letzten Monat begonnene Diskussion über den Bytecode-Befehlssatz der Java Virtual Machine (JVM) fortgesetzt. Dieser Artikel befasst sich mit Gleitkomma-Arithmetik in der JVM und behandelt die Bytecodes, die Gleitkomma-Arithmetikoperationen ausführen. In den folgenden Artikeln werden andere Mitglieder der Bytecode-Familie behandelt.

Die wichtigsten Gleitkommazahlen

Die Gleitkommaunterstützung der JVM entspricht dem Gleitkomma-Standard IEEE-754 von 1985. Dieser Standard definiert das Format von 32-Bit- und 64-Bit-Gleitkommazahlen und definiert die Operationen für diese Zahlen. In der JVM wird Gleitkomma-Arithmetik für 32-Bit-Floats und 64-Bit-Doubles ausgeführt. Für jeden Bytecode, der Arithmetik für Floats ausführt, gibt es einen entsprechenden Bytecode, der dieselbe Operation für Doubles ausführt.

Eine Gleitkommazahl besteht aus vier Teilen - einem Vorzeichen, einer Mantisse, einem Radix und einem Exponenten. Das Vorzeichen ist entweder 1 oder -1. Die Mantisse, immer eine positive Zahl, enthält die signifikanten Ziffern der Gleitkommazahl. Der Exponent gibt die positive oder negative Potenz des Radix an, mit der Mantisse und Vorzeichen multipliziert werden sollen. Die vier Komponenten werden wie folgt kombiniert, um den Gleitkommawert zu erhalten:

Zeichen * Mantisse * Radix Exponent

Gleitkommazahlen haben mehrere Darstellungen, da man die Mantisse jeder Gleitkommazahl immer mit einer Potenz des Radix multiplizieren und den Exponenten ändern kann, um die ursprüngliche Zahl zu erhalten. Beispielsweise kann die Zahl -5 in Radix 10 durch eine der folgenden Formen gleichermaßen dargestellt werden:

Formen von -5
Zeichen Mantisse Radix-Exponent
-1 50 10 -1
-1 5 100
-1 0,5 10 1
-1 0,05 10 2

Für jede Gleitkommazahl gibt es eine Darstellung, die als normalisiert bezeichnet wird. Eine Gleitkommazahl wird normalisiert, wenn ihre Mantisse innerhalb des durch die folgende Beziehung definierten Bereichs liegt:

1 / radix <= mantissa <

Bei einer normalisierten Gleitkommazahl des Radix 10 befindet sich der Dezimalpunkt links von der ersten Ziffer ungleich Null in der Mantisse. Die normalisierte Gleitkommadarstellung von -5 ist -1 * 0,5 * 10 1. Mit anderen Worten, die Mantisse einer normalisierten Gleitkommazahl hat keine Ziffern ungleich Null links vom Dezimalpunkt und eine Ziffer ungleich Null nur bis rechts vom Dezimalpunkt. Jede Gleitkommazahl, die nicht in diese Kategorie passt, wird als denormalisiert bezeichnet . Beachten Sie, dass die Zahl Null keine normalisierte Darstellung hat, da sie keine Ziffer ungleich Null hat, die direkt rechts vom Dezimalpunkt steht. "Warum normalisiert werden?" ist ein häufiger Ausruf unter Nullen.

Gleitkommazahlen in der JVM verwenden einen Radix von zwei. Gleitkommazahlen in der JVM haben daher die folgende Form:

Zeichen * Mantisse * 2 Exponent

Die Mantisse einer Gleitkommazahl in der JVM wird als Binärzahl ausgedrückt. Eine normalisierte Mantisse hat ihren Binärpunkt (das Basis-Zwei-Äquivalent eines Dezimalpunkts) links von der höchstwertigen Ziffer ungleich Null. Da das Binärzahlensystem nur zwei Ziffern hat - Null und Eins - ist die höchstwertige Ziffer einer normalisierten Mantisse immer eine Eins.

Das höchstwertige Bit eines Floats oder Double ist sein Vorzeichenbit. Die Mantisse belegt die 23 niedrigstwertigen Bits eines Floats und die 52 niedrigstwertigen Bits eines Double. Der Exponent, 8 Bits in einem Float und 11 Bits in einem Double, befindet sich zwischen dem Vorzeichen und der Mantisse. Das Format eines Floats ist unten dargestellt. Das Vorzeichenbit wird als "s" angezeigt, die Exponentenbits werden als "e" und die Mantissenbits als "m" angezeigt:

Bit-Layout von Java Float
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmmm

Ein Vorzeichenbit von Null zeigt eine positive Zahl an und ein Vorzeichenbit von Eins zeigt eine negative Zahl an. Die Mantisse wird immer als positive Basis-Zwei-Zahl interpretiert. Es ist keine Zweierkomplementzahl. Wenn das Vorzeichenbit eins ist, ist der Gleitkommawert negativ, aber die Mantisse wird immer noch als positive Zahl interpretiert, die mit -1 multipliziert werden muss.

Das Exponentenfeld wird auf drei Arten interpretiert. Ein Exponent aller Einsen gibt an, dass die Gleitkommazahl einen der speziellen Werte Plus oder Minus unendlich oder "keine Zahl" (NaN) hat. NaN ist das Ergebnis bestimmter Operationen, beispielsweise der Division von Null durch Null. Ein Exponent aller Nullen gibt eine denormalisierte Gleitkommazahl an. Jeder andere Exponent gibt eine normalisierte Gleitkommazahl an.

Die Mantisse enthält ein zusätzliches Bit an Präzision, das über die in den Mantissenbits angezeigten hinausgeht. Die Mantisse eines Schwimmers, die nur 23 Bit belegt, hat eine Genauigkeit von 24 Bit. Die Mantisse eines Doppelpacks, die 52 Bit belegt, hat eine Genauigkeit von 53 Bit. Das höchstwertige Mantissenbit ist vorhersehbar und daher nicht enthalten, da der Exponent der Gleitkommazahlen in der JVM angibt, ob die Zahl normalisiert ist oder nicht. Wenn der Exponent alle Nullen ist, wird die Gleitkommazahl denormalisiert und das höchstwertige Bit der Mantisse ist bekanntermaßen eine Null. Andernfalls wird die Gleitkommazahl normalisiert und das höchstwertige Bit der Mantisse ist bekanntermaßen eins.

Die JVM löst aufgrund von Gleitkommaoperationen keine Ausnahmen aus. Spezielle Werte wie positive und negative Unendlichkeit oder NaN werden als Ergebnis verdächtiger Operationen wie Division durch Null zurückgegeben. Ein Exponent aller Einsen gibt einen speziellen Gleitkommawert an. Ein Exponent aller Einsen mit einer Mantisse, deren Bits alle Null sind, zeigt eine Unendlichkeit an. Das Vorzeichen der Unendlichkeit wird durch das Vorzeichenbit angezeigt. Ein Exponent aller mit einer anderen Mantisse wird als "keine Zahl" (NaN) interpretiert. Die JVM erzeugt immer die gleiche Mantisse für NaN, die alle Nullen sind, mit Ausnahme des höchstwertigen Mantissenbits, das in der Zahl erscheint. Diese Werte werden für einen Float unten angezeigt:

Spezielle Float-Werte
Wert Float-Bits (Vorzeichen-Exponenten-Mantisse)
+ Unendlichkeit 0 11111111 00000000000000000000000
-Unendlichkeit 1 11111111 00000000000000000000000
NaN 1 11111111 1000000000000000000000000

Exponenten, die weder alle Einsen noch alle Nullen sind, geben die Zweierpotenz an, mit der die normalisierte Mantisse multipliziert werden soll. Die Zweierpotenz kann bestimmt werden, indem die Exponentenbits als positive Zahl interpretiert werden und dann eine Vorspannung von der positiven Zahl subtrahiert wird. Für einen Float beträgt die Vorspannung 126. Für ein Double beträgt die Vorspannung 1023. Beispielsweise ergibt ein Exponentenfeld in einem Float von 00000001 eine Zweierpotenz, indem die Vorspannung (126) von dem als positive ganze Zahl interpretierten Exponentenfeld subtrahiert wird (1). Die Zweierpotenz beträgt daher 1 - 126, was -125 ist. Dies ist die kleinstmögliche Zweierpotenz für einen Schwimmer. Im anderen Extrem ergibt ein Exponentenfeld von 11111110 eine Zweierpotenz von (254 - 126) oder 128. Die Zahl 128 ist die größte Zweierpotenz, die einem Schwimmer zur Verfügung steht. In der folgenden Tabelle sind einige Beispiele für normalisierte Floats aufgeführt:

Normalisierte Float-Werte
Wert Float-Bits (Vorzeichen-Exponenten-Mantisse) Unvoreingenommener Exponent
Größter positiver (endlicher) Float 0 11111110 11111111111111111111111 128
Größter negativer (endlicher) Float 1 11111110 11111111111111111111111 128
Kleinster normalisierter Schwimmer 1 00000001 00000000000000000000000 -125
Pi 0 10000000 10010010000111111011011 2

Ein Exponent aller Nullen zeigt an, dass die Mantisse denormalisiert ist, was bedeutet, dass das nicht angegebene führende Bit eine Null anstelle einer Eins ist. Die Zweierpotenz ist in diesem Fall die gleiche wie die niedrigste Zweierpotenz, die einer normalisierten Mantisse zur Verfügung steht. Für den Schwimmer ist dies -125. Dies bedeutet, dass normalisierte Mantissen multipliziert mit zwei, die auf die Potenz von -125 angehoben werden, ein Exponentenfeld von 00000001 haben, während denormalisierte Mantissen multipliziert mit zwei, die auf die Potenz von -125 angehoben werden, ein Exponentenfeld von 00000000 haben. Die Berücksichtigung denormalisierter Zahlen am unteren Rand Das Ende des Exponentenbereichs unterstützt einen allmählichen Unterlauf. Wenn stattdessen der niedrigste Exponent zur Darstellung einer normalisierten Zahl verwendet würde, würde bei größeren Zahlen ein Unterlauf auf Null auftreten. Mit anderen Worten, wenn der niedrigste Exponent für denormalisierte Zahlen belassen wird, können kleinere Zahlen dargestellt werden.Die kleineren denormalisierten Zahlen haben eine geringere Genauigkeit als normalisierte Zahlen, dies ist jedoch dem Unterlaufen auf Null vorzuziehen, sobald der Exponent seinen minimalen normalisierten Wert erreicht.

Denormalisierte Float-Werte
Wert Float-Bits (Vorzeichen-Exponenten-Mantisse)
Kleinster positiver Float (ungleich Null) 0 00000000 00000000000000000000001
Kleinster negativer Float (ungleich Null) 1 00000000 00000000000000000000001
Größter denormalisierter Schwimmer 1 00000000 11111111111111111111111
Positive Null 0 00000000 00000000000000000000000
Negative Null 1 00000000 00000000000000000000000

Freiliegender Schwimmer

Ein Java-Float enthüllt seine innere Natur Mit dem folgenden Applet können Sie mit dem Gleitkommaformat herumspielen. Der Wert eines Floats wird in verschiedenen Formaten angezeigt. Das wissenschaftliche Notationsformat von Radix 2 zeigt die Mantisse und den Exponenten in der Basis zehn. Vor der Anzeige wird die tatsächliche Mantisse mit 2 24 multipliziert, was eine ganzzahlige Zahl ergibt, und der unverzerrte Exponent wird um 24 dekrementiert. Sowohl die integrale Mantisse als auch der Exponent werden dann leicht in die Basis 10 umgewandelt und angezeigt.