Das Floating-Point-Format im Detail
Teil 2 von 3 / 8. März 2004 / von aths / Seite 7 von 13
Inf und NaN - für alles gerüstet
Bei unserem FP5-Beispiel war die größte darstellbare Zahl 3,75. Was tut man, wenn eine Operation einen größeren Wert zurück liefert, als überhaupt darstellbar ist? Dieses Problem heißt Überlauf. Hier sollte das Rechenwerk einen speziellen Vermerk setzen, anstatt einfach bei der größten darstellbaren Zahl abzuschneiden. Das tut es auch, in dem das Ergebnis auf "Inf" (infinity, unendlich) gesetzt wird.
Was passiert, wenn man andere Operation ausführt, dessen Ergebnis nicht definiert ist? Man könnte zum Beispiel auf die Idee kommen, die Quadratwurzel aus -1 ziehen zu lassen. Im reellen Zahlenbereich gibt es dafür keine Lösung. Das Rechenwerk sollte dann auch nicht einfach irgendeine Zahl zurückliefern, sondern den Vermerk, dass es keine Lösung gibt. Diesen Vermerk nennt man im englischen "Not a number", abgekürzt NaN. Für beide Fälle wurden spezielle Bitmuster reserviert.
Für NaN und für Inf ist beim physikalischen Exponenten jede Bitstelle auf 1 gesetzt. Ist die Mantisse durchgehend 0, steht das Bitmuster in seiner Gesamtheit für Inf. Außerdem wird das Vorzeichen ausgewertet, so dass man zwischen +Inf und -Inf unterscheidet. Ist die Mantisse nicht durchgehend 0, also irgendwo minstestens ein Bit gesetzt, haben wir NaN. Die Vorzeichen unterscheidet man dann aber nicht mehr.
Wird mit NaN weitergerechnet, ist das Ergebnis ebenfalls NaN. Somit kann sichergestellt werden, dass nur dann ein gültiges Ergebnis am Ende steht, wenn jede einzelne Rechenoperation zu einem gültigen Zwischenergebnis führte. Sehen wir uns diesen scheinbar blödsinnigen Test an:
if (x == x)
Der Compiler kann "x == x" nicht einfach zu true optimieren, sofern x eine Floating-Point-Zahl ist. Denn bei x=NaN ist liefert der Test false. NaN ist ungleich alles, auch ungleich NaN.
Mit Inf allerdings kann weitergerechnet werden. Das Ergebnis der Operation 1 / Inf ist gleich 0. Und 1 / (-Inf) ist -0. Bei Vergleichen ergibt (Inf == Inf) true. Damit man bei einem Vergleich nicht aus Versehen true bekommt, nur weil bei beiden Werten in einer vorherigen Rechnung ein Überlauf auftrat, würde eine CPU beim Überlauf eine Exception werfen. Eine GPU arbeitet jedoch unbeirrt weiter, den Komfort von CPUs haben Grafikchips noch nicht erreicht.
Grenzen des Wertebereiches am Beispiel FP16
Wie groß kann eine FP16-Zahl sein, bevor sie für "unendlich" gehalten wird? Der höchste logische Exponent, der für eine Zahl steht, ist 30-15 = 15. Bei 5 Bits für den Exponenten hat man maximal binär 11111 = 31, aber dieses Muster ist ja für "Inf" reserviert. Also müssen wir einen Exponent kleiner gehen, das ist 11110 = 30, dazu das Bias von -15, welches für FP16 gilt. Inklusive der impliziten 1 vor der 10-Bit-Mantisse haben wir als Faktor die binäre 1,1111111111. Das Komma müssen wir um die 15 Stellen verschieben und gelangen zu 1111111111100000,0 was dezimal 65504 entspricht. Ein Rechenergebnis größer als 65504 führt bei FP16 zum Overflow, und die Zahl wird auf Inf gesetzt.
Natürlich gibt es auch das Gegenteil, wenn eine Zahl so klein wird, dass man sie zu Null rundet, dann kommt es zum Underflow. Dank Denorms gelangt man ja sehr dicht an die Null. Allerdings geht hierbei Genauigkeit verloren. Sehen wir uns eine Zahl an, deren ersten 17 binären Stellen nach dem Komma 0 sein sollen. Man bräuchte einen Exponenten von -18. Kleiner als -14 kommen wir aber nicht.
Bei einer normalisierten Mantisse wird eine führenden "1," implizit angenommen, genau das wird beim Denorm weggelassen, diese Stelle hat damit die Wertigkeit 0. Demnach müssen noch 3 weitere Stellen am Anfang der denormalisierten Mantisse 0 sein. Statt 10 relevanten Stellen in der Mantisse haben wir nur noch 7 (3 weniger als 10). Streng genommen sogar nur 6 Bits, da die implizite 1 einer normalisierten Mantisse jetzt explizit angegeben werden muss.
Von den 10 Bits der Mantisse werden nur noch 6 für den eigentlichen Faktor genutzt, weshalb man auch vom gradual underflow spricht: Noch muss nicht auf Null abgeschnitten werden, doch die vorher garantierte 10-Bit-Genauigkeit ist nicht mehr gegeben, je kleiner die Zahl, desto weniger relevante Binärstellen können gespeichert werden. Das betrachten wir noch einmal schrittweise auf einer extra Seite, wo der gradual underflow sozusagen "bitfein" aufgeschlüsselt wird.
Die Bestimmung der Genauigkeit
Man könnte die berechtigte Frage stellen, wozu der ganze Aufwand mit Floating-Point-Logik getrieben wird. Gut, der relative Fehler wird jetzt gleichmäßiger über die Zahl verteilt, aber ist FP16 zum Beispiel genauer als FX12? Um so etwas zu beurteilen, wird oft das Kriterium der Maschinengenauigkeit herangezogen. Dazu sucht man die kleinste darstellbare Zahl, die gerade so größer als 1,0 ist. Der Abstand zur 1,0 ist die "Maschinengenauigkeit", also ein eher "allgemeines" Genauigkeits-Maß. Sonderfälle wie gradual underflow spielen hier keine Rolle.
Diese gesuchte Zahl "knapp größer als 1" ist bei einer FP-Zahl im Bereich von 1 bis "knapp 2" zu suchen. Der logische Exponent ist demzufolge 0, da 2^0 = 1. Der Faktor entspricht binär 1,00'0000'0001. Die führende 1 wird bekanntlich nicht mitgespeichert, und im Nachkomma-Bereich stehen ja 10 Binärstellen zur Verfügung. Da binär 1,00'0000'0001 die kleinste darstellbare Zahl größer als 1 ist, ist die Maschinengenauigkeit binär 0,00'0000'0001 (mit +1 gelangen wir dann wieder zur kleinsten darstellbaren Zahl größer als 1).
Binär 0,0000000001 wird natürlich so gespeichert: 1,0000000000 + Verschiebung des Kommas um 10 Stellen nach links. Mathematisch entspricht das 1 * 2^-10 = 0,0009765625. Das ist die Maschinengenauigkeit vom FP16-Format.
Bei FX12 stellt sich die Sache - mehr oder minder zufällig - sehr ähnlich dar: Die kleinste darstellbare Zahl größer als 1,0 ist binär 1,00'0000'0001, die Maschinengenauigkeit ist binär 0,00'0000'0001. Nun ist bei FX12 das Komma fest, so dass es immer 10 Nachkomma-Stellen gibt. Zur Erinnerung: FP16 nutzt eine binäre Vorkommastelle und 10 binäre Nachkomma-Stellen, wobei man jedoch die Komma-Position durch den Exponenten verschieben kann. So lassen sich Zahlen darstellen, die beispielsweise 2 Vorkomma-Stellen haben, natürlich bleiben dann nur noch 9 Nachkomma-Stellen.
Lässt sich vielleicht ein Bereich finden, in dem FX12 genauer als FP16 ist? Beim FP-Format nimmt die absolute Genauigkeit ja ab, wenn die Zahlen größer werden. Im Bereich von 1 bis "knapp 2" haben wir die soeben berechnete Maschinengenauigkeit. Im Bereich von 2 - "knapp 4" halbiert sich die Genauigkeit - da sich die Schrittweite zwischen den darstellbaren Zahl verdoppelt. Allerdings ist FX12 auf "knapp 2" begrenzt, höher kommt man damit nicht.
Bei kleineren Zahlen, zum Beispiel im Bereich von 0,5 bis "knapp 1", ist FP16 ohnehin besser als FX12, da sich die Genauigkeit erhöht (durch eine Halbierung der Schrittweite zwischen den einzelnen darstellbaren Zahlen). FX12 hat im Bereich von 1 bis 2 insgesamt 1024 Abstufungen. FP16 ebenfalls, aber die 1024 Abstufungen stehen auch für kleinere Bereiche zur Verfügung, wie z. B. 0,5 bis 1,0 oder 0,25 bis 0,5. Von diesen Bereichen kleiner 1 gibt es insgesamt 15 Stück (da der kleinste logische Exponent = -14 ist, plus ein Denorm-Bereich) und somit wird der Bereich von 0 bis 1 in 15 * 1024 = 15360 Abstufungen unterteilt. FX12 hingegen muss mit 1024 Stufen den ganzen Bereich von 0 bis 1 abdecken.
Eine andere Definition für Maschinengenauigkeit fragt nach ε (Epsilon). In diesem Zusammenhang versteht man unter ε die kleinste Zahl, die auf 1 addiert, als Ergebnis eine Zahl größer 1 hat. Das ε entspricht bei FP16 etwa der halben Maschinengenauigkeit, es muss ein wenig größer sein als 0,00048828125. Addiert man diese Zahl auf die 1, rundet das Rechenwerk auf 1,0009765625. Ein so kleines ε ist zwar mit FP16 darstellbar, jedoch nicht mit FX12.
Man sieht: FP16 ist niemals schlechter, sondern oftmals besser als FX12. Gegenüber FX16 jedoch wäre FP16 in gewissen Bereichen im Nachteil. Das Floating-Point-Format braucht ein paar Bits mehr, um garantiert gegenüber einem Fix-Point-Format nirgendwo schlechter zu sein, bietet jedoch Vorteile, die ein gleich langes FX-Format nicht vorweisen kann. Natürlich ist FP16 nicht so überragend gut, dass es für alles reichen würde.