System.out.print(445.75f)

System.out.print(445.75f)

Wie ich zu dieser Subdomain kam

·

3 min read

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:

math001

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)

math002

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