Hagen Völzke

Fließkomma-Arithmetik und IEEE-Spezifikationen

Teil 4: Die Konvertierungsroutinen

Mit den Grundrechenarten ist ein Fließkommapaket noch nicht fertig - schließlich will man die Zahlen ja nicht in diesem Format eingeben. Dieser Teil behandelt die Konvertierung zwischen Fließkommazahlen und anderen Darstellungen, zunächst der Integerdarstellung.
Beim Arbeiten mit Fließkommazahlen werden verschiedene Umwandlungen benötigt. Für die Ein- und Ausgabe durch den Anwender braucht man die Umwandlung von Fließkommazahlen in Zeichenketten und umgekehrt, bei vielen Berechnungsvorgängen die Umwandlung von Fließkommazahlen in Integerwerte und zurück. Zunächst wird hier die Konvertierung in Integer bzw. von Integer dargestellt. Da diese Funktionen sehr maschinennah sind, erfolgte die Realisierung noch in Assembler. Die (ebenfalls maschinennahe) Umwandlung von ASCII in Fließkomma bzw. umgekehrt ist wegen ihrer Komplexität in einer höheren Sprache realisiert. Dazu wurden wegen der guten Geschwindigkeit und Verbreitung die Sprache C gewählt.
Bevor wir uns jedoch den Konvertierungsverfahren zuwenden, soll die Genauigkeit von Fließkommazahlen betrachtet werden.

Rechengenauigkeit der Fließkommazahlen

Bislang wurde die Genauigkeit immer nur in Bits bzw. Binärstellen betrachtet. Die Bedeutung für den Anwender ging daraus noch nicht klar hervor, da dieser ja normalerweise im Dezimalsystem rechnet. Betrachten wir eine Fließkommazahl mit 24-Bit-Mantisse (single-precision). Mit diesen 24 Bit läßt sich der Zahlenbereich 0...224-1 = 16777215 abdecken. Das entspricht etwas mehr als sieben Dezimalstellen. Und das ist die Zahl, die uns interessiert. Man kann sie mit Hilfe folgender Formel berechnen:

n = log(16 777 215);

wobei n die Zahl der Dezimalstellen ist und log() der Logarithmus zur Basis Zehn. Als Ergebnis erhalten wir

n = 7,2247...

Wir müssen n immer zur nächsten kleineren Zahl abrunden, um die Zahl der genauen Dezimalstellen zu erhalten, da man Bruchteile von Stellen ja nicht ablesen kann. „Genau" sind in diesem Fall also höchstens sieben Dezimalstellen. Im Fall einer double-precision-Fließkommazahl mit 52-Bit-Mantisse ergibt sich:

n=log(252);
=15.6535...

Hier sind höchstens fünfzehn Dezimalstellen „genau". Es ist daher nicht sinnvoll, eine double-precision-Zahl mit mehr als 15 Stellen auszugeben (bzw. single-precision mit mehr als 7 Stellen). Spätestens auf der sechzehnten (bzw. achten) Stelle treten zwangsläufig Ungenauigkeiten auf, da der Computer diese Stellen überhaupt nicht mehr abspeichern kann: Hier würde nur eine Genauigkeit vorgetäuscht.
Wie schon im ersten Teil des Artikels erwähnt, sind bereits „einfache" Zahlen wie z.B. 0,1 im Computer als periodischer Binärbruch gespeichert und können bestenfalls auf 15 bzw. 7 Stellen genau ausgegeben werden. Für eine wissenschaftliche Zahlendarstellung bedeutet dies: Eine Vorkomma-Stelle und 14 bzw. 6 Nachkommastellen.
Dazu ein Beispiel: Die Zahl 1,234 wird in den Computer eingelesen. Bei der Ausgabe sind folgende Ergebnisse zu erwarten:

1.2340 00000 00000 ?? (double-precision)
1.2340 00?? ... (single-precision)

Die Leerzeichen zwischen den Ziffern dienen hier nur zur optischen Trennung. Die Fragezeichen deuten Stellen an, auf denen praktisch willkürliche Ziffern erscheinen können. Zu einer gegebenen Zahl von Binärstellen läßt sich die Zahl der genauen Dezimalstellen durch folgenden Umrechnungsfaktor berechnen:

n = b / 3,32; (mit b = Zahl der Binärstellen)

Das Ergebnis muß auch hier ggf. zur nächstkleineren ganzen Zahl abgerundet werden. Der Umrechnungsfaktor berechnet sich nach folgender Formel:

f = 1 / log(2) = 3,32...

Integer-Konvertierung

Bilder 1...3 zeigen jeweils ein Listing für Prozessoren der Familien 68000 und 8086 sowie für den Z80.
Bild 1. Die Routinen für die Prozessoren der 68000-Familie
Nicht ausgehängt.
Bild 2. So sehen die Routinen für den 8086 und Kompatible aus
Nicht ausgehängt.
Bild 3. Die Z80-Routinen sind wieder mal die längsten
[Source file]
Die Listings sind Ergänzungen zu den Programmen in den vergangenen Heften. Sie sollten dort per INCLUDE-Anweisungen eingebunden werden, da sie einige Definitionen aus diesen Listings mitbenutzen.
Die erste Funktion f_ltof bewirkt die Konvertierung von Long-Integer-Zahlen zur Fließkomma-Darstellung. Standardmäßig werden lange Integerzahlen, also 32-Bit Zahlen, unterstützt.

Die Konvertierung einer Integer-Zahl in eine Fließkommazahl bereitet kaum Probleme. Dazu betrachten wir das Flußdiagramm in Bild 4.
Bild 4. Flußdiagramm zur Konvertierung LONG-INTEGER TO FLOAT
Zunächst wird die Zahl auf Null geprüft. Ein Integer-Wert von Null ergibt natürlich auch den Fließkommawert Null und muß nicht berechnet werden. Die Sonderbehandlung der Null ist erforderlich, da sonst aus der folgenden Schleife eine Endlosschleife werden könnte. Liegt eine negative Zahl vor, so muß sie negiert werden, da in der Fließkommadarstellung stets eine positive Mantisse gespeichert wird. Das Vorzeichen muß für das spätere Ergebnis aufbewahrt werden. In der besagten Schleife wird nun die Integerzahl solange nach links verschoben, bis eine Eins „überläuft", d.h. ins Carryflag geschoben wird. Während der Schleifendurchläufe wird außerdem der Exponent mitgezählt, so daß jetzt nur noch das Ergebnis zusammengesetzt werden muß.
Während die f_ltof-Funktion beim Prozessor 68000 mit nur 16 CPU-Befehlen auskommt, füllt das Programm für den Z80 schon wieder eine ganze Seite. Insbesondere die Abfrage auf Null sowie die Negierung der Integerzahl ergeben schon längere Programmsequenzen, die das Listing zu einem großen Teil füllen.

Die Funktion f_ftol

Die Funktion f_ftol kehrt die Operation der Funktion f_ltol gerade um: aus einer Fließkommazahl wird wieder eine Integerzahl gemacht. Diese Umkehrung ist allerdings nicht vollständig, denn eine Fließkommazahl verwendet lediglich eine 24-Bit-Mantisse, während eine Integerzahl 31 Bit lang ist (plus 1 Bit Vorzeichen). Je nach Größe der Integerzahl werden also bei der Konvertierung von Integer in Fließkomma bis zu 7 Bit einfach abgeschnitten, die natürlich bei einer späteren Rückwandlung nicht mehr vorhanden sind. Die Funktion f_ftol extrahiert zunächst den Exponenten der Fließkommazahl und subtrahiert anschließend den Bias. (Siehe auch Flußdiagramm Bild 5.)
Bild 5. Flußdiagramm zur Konvertierung FLOAT TO LONG-INTEGER
Ist der resultierende Exponent kleiner als Null, so liegt der Wert der Fließkommazahl unter Eins, damit ergibt der Integerwert Null. Liegt der Exponent über 30, so ist die Fließkommazahl zu groß. Man könnte nun einen Überlauffehler erzeugen. Im Programm wird für diesen Fall MAXINT (bzw. MININT) zurückgegeben.

Auch die Funktion f_ftol benötigt eine Schiebeschleife. In dieser werden die Bits der Mantisse wieder rechtsbündig positioniert, um den korrekten Integerwert herzustellen. Am Schluß muß noch das Ergebnis negiert werden, falls die Fließkommazahl negativ war. Integerwerte werden stets im Zweierkomplement dargestellt, Fließkommawerte dagegen durch einen Absolutbetrag (Mantisse) und ein getrenntes Vorzeichenbit.

Multiplikation mit Zweierpotenzen

Als dritte und letzte Assembler-Funktion stellen wir die Multiplikation mit Zweierpotenzen vor, also z.B.

3.14*2,
5.6789 * 8,
1.245 * 0.5
usw.

Das kommt in der Praxis häufig vor und läßt sich sehr effizient lösen. Deswegen gibt es für diesen Spezialfall der Fließkommamultiplikation eine eigene Funktion. Wenn man mit Integerzahlen rechnet, läßt sich eine Multiplikation mit Zweierpotenzen bequem und schnell durch Schiebebefehle realisieren. Bei Fließkommazahlen ist im Prinzip nur eine Erhöhung bzw. Verringerung des Exponenten nötig, die Mantisse bleibt in der Regel unberührt.
Die Funktion f_mul2 wird mit zwei Parametern aufgerufen. Der erste Parameter ist eine Fließkommazahl, der zweite ein Integerwert, der den Exponenten der Zweierpotenz angibt. Zum Aufruf drei Beispiele:

f_mul2(fpzahl,1) ≡ fpzahl * (21) = fpzahl * 2
f_mul2(fpzahl,4) ≡ fpzahl * (24) = fpzahl * 16
f_mul2(fpzahl,-2) ≡ fpzahl * (2-2) = fpzahl * 0,25 = fpzahl / 4

Bei Angabe eines negativen Exponenten ist also auch die Division durch Zweierpotenzen möglich.

Die Multiplikation mit Zweierpotenzen ist ein Spezialfall der allgemeinen Fließkommamultiplikation: f_mul2 prüft zunächst ab, ob die Fließkommazahl Null ist. Dann ist auch das Ergebnis Null, und es kann sofort abgebrochen werden. Andernfalls wird nun der Exponent der Fließkommazahl extrahiert. Nun muß leider eine Fallunterscheidung vorgenommen werden, denn die Fließkommazahl könnte ja denormalisiert sein. Dann nämlich muß bei der Multiplikation zunächst die Zahl normalisiert werden, bevor der Exponent verändert werden darf. Ein ähnlicher Fall tritt bei der Division auf: es entsteht ein Exponenten-Unterlauf, und das Ergebnis muß denormalisiert werden. Beim Durchlauf mit normalisierten Zahlen werden lediglich die beiden Exponenten addiert, und das Ergebnis wird wieder zusammengesetzt. Die Sonderbehandlungen, die das Listing wieder etwas in die Länge ziehen, sind nur für die denormalisierten Zahlen von Belang und werden auch nur für diese durchlaufen. Gegenüber der normalen Fließkommamultiplikation spart man sich die Mantissenmultiplikation sowie fast alle anderen Behandlungen der Mantisse. Über- und Unterlauf müssen natürlich auch abgefangen werden und produzieren jeweils MAXFLOAT bzw. Null. Alternativ kann auch hier wieder eine Fehlerbehandlung eingesetzt werden. Der Geschwindigkeitsgewinn gegenüber der normalen Multiplikation ist sehr hoch.
[3. Teil] [Inhalt]

Scanned by Werner Cirsovius
September 2004
© Franzis' Verlag