Präambel
Floating Point Numbers
gehören zum täglichen Leben von Entwickler:innen. Doch manchmal wissen sie auch noch zu überraschen. So erging es zum Beispiel mir vor ein paar Jahren als ich auf Hexadecimal Floating Point Literals
stieß.
Hexadecimal Floating Point Literals
befinden sich offenbar seit mindestens 2003 im OpenJDK und waren damit wohl Teil der berüchtigten J2SE 5.0
Version, die einige Neuerungen in die Sprache mitgebracht hat. In der aktuellen Java 17 Dokumentation findet sich auch ein Hinweis darauf, dass eine Methode, auf die wir später noch zurückkommen werden auch seit 1.5
in Java SE
verfügbar ist. Doch die JDK-Entwickler:innen haben sich das Ganze nicht ausgedacht. Einzug fanden Hexadecimal Floating Point Literals
bereits in den C99
Standard.
Was ist ein Hexadecimal Floating Point Literal
?
Eine Floating Point Number
ist so etwas wie 0.765625
und eine Hexadecimal Number
ist z.B. 0xc4
. Jetzt brauchen wir das Ganze nur noch zusammenführen und erhalten 0x0.c4p0
. Was bedeutet das nun? 0x
ist ein Präfix, der dem Compiler signalisiert, es handelt sich um eine Hexadezimalzahl. Als nächstes folgt die Basis der Hexadezimalzahl, die aus zwei Komponenten besteht, getrennt durch den Punkt. Dazu später mehr. p0
signalisiert, dass nun der Exponent, in unserem Fall 0
, folgt. Dieser wird in Dezimalschreibweise angegeben. Hinter der Zahl 0x0.c4p0
versteckt sich die Dezimalzahl 0.765625
. In Java sollte also folgendes Programm true
ausgeben:
class HexadecimalFloatingPointTest {
public static void main(String[] args) {
System.out.println(0.765625f == 0x0.c4p0f);
}
}
Let's do the math
Wie kommt man nun von 0x0.c4p0
auf 0.765625
? Nehmen wir dazu die Hexadezimalzahl auseinander und rechnen ein bisschen:
Wie wir sehen geben also die Ziffern vor dem Punkt die Sechzehnerpotenzen an und die Ziffern hinter dem Punkt die Sechzehntelpotenzen. Hexadezimalzahlen gehen von 0
bis f
, müssen also in ihre Wertigkeit umgewandelt werden. In unserem Fall wird aus c
12
. Zum Schluss wird dann noch mit einer Zweierpotenz multipliziert. Versuchen wir noch ein Beispiel: 0x1f7.ab3p4f
(Das f
am Ende signalisiert dem Java Compiler nur, dass es sich um ein float
handelt, ist also Java spezifisch, ansonsten würde der Compiler die Zahl als double
interpretieren, was für unsere Beispiele aber irrelevant ist)
Das folgende Java Programm wandelt ein dezimales float
in die Hexadezimalform um:
class HexadecimalFloatingPointTest {
public static void main(String[] args) {
System.out.println(Float.toHexString(8058.699f));
}
}
Das Ergebnis lautet: 0x1.f7ab3p12
, was eine andere Darstellung für 0x1f7.ab3p4
ist. Man kann den Punkt beliebig wandern lassen, indem man den Exponenten anpasst und um 4
erhöht oder verringert, da: 2 ^ 4 = 16
. Folgende Hexadezimalzahlen haben also alle denselben Dezimalwert 8058.699
:
0x1f.7ab3p8
0x.1f7ab3P16
0x1f7a.b3p0
0x1f7ab.3P-4
0x1f7ab3.p-8
Epilog
Doch was hat das nun alles mit der Subdomain zu tun? Nun, ganz einfach, der Titel verrät es ja bereits. Die Dezimalzahl 445.75
kann man als Hexadezimalzahl 0x.deep9
darstellen. As simple as that.
In anderen Programmiersprachen wie Python kann man übrigens den 0x
Präfix auch weglassen, in Java hingegen nicht.
float.fromhex('.deep9') == 445.75