Fließkomma-Arithmetik und IEEE-Spezifikationen
Teil 1: Standards und Strukturen
Numerische Probleme sind eine häufige Computer-Anwendung, wobei meist nicht nur hohe Rechengeschwindigkeit gefordert, sondern auch Wert auf hohe Genauigkeit gelegt wird.
Dieser Beitrag befaßt sich mit Standards und Datenstrukturen, die beim Rechnen mit Computern Verwendung finden.
|
Betrachten wir zu Anfang zwei einfache Ausdrücke:
x = (1 + 1e-20)- 1 und
x = (1 - 1) + 1e-20.
Anscheinend ergeben beide Ausdrücke das gleiche Ergebnis für x.
Fragt man jedoch den Computer, so liefert er im ersten Fall als Resultat 0, im zweiten Fall hingegen das erwartete Ergebnis von 10-20.
Offenbar wurden hier fundamentale Gesetze der Mathematik verletzt:
das Assoziativgesetz und das Kommuativgesetz.
Lediglich durch Umstellen und andere Klammerung wird der Ausdruck vollkommen unterschiedlich berechnet, obwohl mathematisch das gleiche Ergebnis zu erwarten ist.
Hier spielt ganz offensichtlich die Reihenfolge der Auswertung eine große Rolle für die Korrektheit des Ergebnisses.
Eine Division durch das Ergebnis dieser Operation führt im ersten Fall zu einer Division durch Null, also einem fatalen Programmfehler, im zweiten Fall jedoch wird fehlerfrei weitergearbeitet.
Derartige Fehler können also große Probleme nach sich ziehen.
Dabei wurden doch nur die Operationen Subtraktion und Addition benutzt.
Aber gerade die Subtraktion ist die labilste arithmetische Operation, die ein Computer durchführen kann - die komplizierteren Divisionen und Multiplikationen bereiten oft weit weniger Probleme.
Die Numerik
Die Numerik ist eines der Spezialgebiete der Mathematik, das durch die Computerära noch interessanter wurde.
In diesem Fachgebiet beschäftigen sich die Mathematiker auch mit all den Problemen, Tricks und Methoden, die auftreten, wenn man dem Computer das Rechnen beibringen will.
Dabei entwickelt die Numerik ihre eigenen Rechenregeln.
Assoziativgesetz und Kommutativgesetz sowie einfache Umkehrregeln gelten nur noch bedingt, oftmals abhängig von den Eingabedaten, so daß sich Rechenfehler nur schwer von vornherein abwenden lassen.
Fast alle Mikroprozessoren beherrschen zunächst nur den Umgang mit ganzen Zahlen, den sogenannten Integer-Zahlen.
Doch für die täglichen Berechnungen reicht das nicht aus:
Bereits beim Addieren von Geldbeträgen, wie es in jeder Buchhaltung vorkommt, benötigt man auch Bruchteile von Einheiten, eben Pfennige und manchmal sogar Bruchteile von diesen (etwa bei Benzinpreisen).
Bei Geldbeträgen könnte man nun auf die Idee kommen, immer mit Pfennigen zu rechnen, denn dann hätte man ja wieder ganze Zahlen.
Bei der Ein- und Ausgabe in der Einheit „Mark" kann man dann jeweils das Komma (bzw. den Dezimalpunkt) berücksichtigen.
Diese Form der Arithmetik nennt man Fixpunktarithmetik, da mit einer festen Kommaposition gearbeitet wird.
Während es im europäischen Raum üblich ist, Bruchteile von Einheiten durch ein Komma abzutrennen, wird im amerikanischen Raum ein Punkt benutzt.
Die Computer, die vor allem durch amerikanische Einflüsse geprägt wurden, verwenden normalerweise den Punkt zur Trennung.
„23,45 DM" entspricht also „23.45 DM".
Wir wollen daher unter den Begriffen Fixpunkt-, Fixkomma-, Festpunkt-, und Festkomma-Arithmetik das gleiche verstehen.
Während eine feste Lage des Kommas recht einfach im Computer zu handhaben ist, wird es komplizierter, wenn man das Komma verschieben will, je nachdem ob man mit besonders großen oder kleinen Zahlen rechnen möchte.
Man spricht dann auch von gleitender oder fließender Kommastellung.
Ein Programmpaket, das ein gleitendes Komma unterstützt, heißt im Fachjargon „floating-point"-Paket.
Die Exponential-Darstellung
Während man sich eine feste Lage des Kommas nur für die Ein- und Ausgabe merkt, muß die Lage eines verschiebbaren Kommas zusammen mit der „eigentlichen" Zahl abgespeichert werden.
Die „eigentliche" Zahl ist dann eine Ziffernfolge, man nennt sie die Mantisse.
Die Kommastellung relativ dazu wird durch den Exponenten angegeben.
Unseren DM-Betrag von oben kann man also durch die Mantisse 2345 und den Exponenten -2 abspeichern:
Eine Zahl mit Komma ist also durch zwei ganze Zahlen dargestellt.
Dabei bedeuten negative Exponenten eine Kommaverschiebung nach links, positive eine nach rechts.
Man kann jetzt auch Zahlen darstellen, die wesentlich größer sind, als der normale Integerbereich es zulassen würde.
Dafür benutzt man die Kommaverschiebung nach rechts.
Die Zahl 1 000 000 000 (1 Milliarde) etwa läßt sich durch eine Mantisse von 1 und einen Exponenten von 9 darstellen.
Man schreibt 1e9, wobei das „e" als Trennung zwischen Mantisse und Exponent dient.
In dieser Form, die schon im Beispiel am Anfang des Artikels verwendet wurde, versteht auch ein Computer die Eingabe dieser Zahl.
Bei unserem DM-Betrag setzten wir stillschweigend voraus, daß das Komma beim Exponenten = 0 hinter der letzten Ziffer der Mantisse (also am rechten Ende) stehen würde.
In der Praxis verwendet man allerdings die sogenannte „normalisierte Darstellung".
Dabei steht das Komma hinter der ersten Ziffer, die ungleich Null ist.
Diese Darstellung ist auch als wissenschaftliche Darstellung bekannt und wird von allen gängigen Taschenrechnern unterstützt.
Unser DM-Betrag von „23,45" wird dann repräsentiert durch die Mantisse „2,345" und den Exponenten „1".
Eine normalisierte Dezimalzahl beginnt also stets mit einer Ziffer zwischen „1" und „9", gefolgt von einem Komma und eventuell weiteren Ziffern.
Eine normalisierte Binärzahl hingegen beginnt stets mit einer „1", da ja im Binärsystem nur zwei Ziffern existieren, nämlich „0" und „1".
Dieser ersten Eins folgen dann das Komma sowie weitere Ziffern.
Die Mantisse kann nun in zwei Teile zerlegt werden:
Den Vorkommateil (also die führende „1") und den Nachkommateil.
Letzteren nennt man auch „fraction", also Bruchteil.
Die Nachkommastellen bezeichnet man daher auch als Binärbruch.
Tabelle 1 gibt weitere Erläuterungen zur Exponentialdarstellung.
Tabelle 1: Mantisse und Exponent
|
Die Schreibweise „1e9" ist eine Abkürzung für 1 x 109, also für den Wert 1 000 000 000, eine Eins mit neun Nullen.
Man nennt dies die Exponentialdarstellung.
Im Binärsystem lautet die Exponentialdarstellung beispielsweise: „1e101" (ist eine Abkürzung für 1 x 25).
Das Zeichen „e" wird also immer gegen die Basis des jeweiligen Zahlensystems ausgetauscht.
Die Zahl 100,0 entspricht 1,0 x 102 - eine Verschiebung des Kommas nach links entspricht einer Erhöhung des Exponenten.
Die Zahl 0,001 entspricht 1,0 x 10-3 - eine Verschiebung des Kommas nach rechts entspricht einer Verringerung des Exponenten.
|
|
Bei der Abspeicherung von Fließkommazahlen im Computer verwendet man in der Regel Vielfache von Bytes, z.B. 2 Byte (16 Bit) oder 4 Byte (32 Bit) oder auch noch mehr.
Das Rechnen mit überlangen Integerzahlen wird von fast jedem Mikroprozessor unterstützt.
Mit Hilfe eines Übertrags-Bits (Carry) kann man die Rechenoperationen einer CPU auf nahezu beliebig lange Integerzahlen ausdehnen.
Wählt man zur Darstellung einer Zahl für die Mantisse n Bits, so lassen sich 2n verschiedene Mantissen darstellen.
Verwendet man für den Exponenten m Bits, so gibt es 2m verschiedene Exponenten.
Es lassen sich also 2n x 2m verschiedene Gleitkommazahlen im Computer darstellen (nicht berücksichtigt, daß durch die normalisierte Darstellung eine weitere Einschränkung vorgenommen wird).
In der Praxis eingesetzte Werte sind zum Beispiel n = 24 und m = 7.
Damit kann man einen Zahlenbereich von 1,2e-38 bis 3,4e+38 abdecken.
Das klingt eigentlich ganz gut...
Ein Beispiel
Betrachten wir eine Fließkommadarstellung, bei der für die Mantisse 4 Bit und für den Exponenten 2 Bit verwendet werden.
Bei normalisierter Darstellung können damit 32 verschiedene Zahlenwerte dargestellt werden, 33 wenn man die Null mit dazu nimmt.
Das erste Bit der Mantisse muß ja immer eine „1" sein, daher erhalten wir nur acht verschiedene Mantissenwerte statt der erwarteten 16 (16 = 24).
In Bild 1 wurden die ersten 24 normalisierten Fließkommazahlen sowie die Null auf einem Zahlenstrahl eingetragen.
Bild 1. Der Zahlenstrahl bei verschiedenen Darstellungen |
|
Bei der Betrachtung von Bild 1 fallen mehrere Dinge auf:
- Mit wachsendem Exponentem nimmt der (absolute) Abstand zwischen den einzelnen Zahlenwerten zu.
- Zwischen dem Wert 0 und der ersten Zahl ungleich Null besteht eine verhältnismäßig große Lücke.
Diese Lücke bereitet vor allem bei Divisionen Schwierigkeiten.
So gibt zum Beispiel für die im Bild 1 markierten Werte a und b folgende Befehlssequenz trotz Sicherheitabfrage den Fehler „Division durch Null":
if a <> b then x:= 1 / (a - b)
Der Grund: a ist offensichtlich ungleich b.
Mathematisch erwartet man also, daß die Differenz von a und b ungleich Null ist, die anschließende Division im „
then
"-Zweig also erlaubt ist.
Bei der Berechnung der Differenz von a und b jedoch fällt das Ergebnis in die Lücke vor der Null und das berechnete numerische Resultat ist Null.
Folgende Programmsequenz würde den Fehler vermeiden:
if (a-b) <> 0 then x:= 1 / (a - b)
Hier wird schon bei der Abfrage im „
if
"-Zweig die echte Differenz gebildet und der „
then
"-Zweig nicht mehr durchlaufen.
Die Maschinenzahlen
Wir nennen die Zahlen, die der Computer exakt darstellen kann, im Unterschied zu den reellen Zahlen „Maschinenzahlen".
Man kann die oben begonnene Liste der Auffälligkeiten von Bild 1 fortsetzen:
- Es gibt keine beliebig großen, beliebig kleinen oder beliebig dicht nebeneinander liegenden Maschinenzahlen.
- Werden zwei Maschinenzahlen miteinander verknüpft (addiert, subtrahiert,...), so ist das Ergebnis im allgemeinen keine Maschinenzahl (siehe obiges Beispiel).
Es treten also an mehreren Stellen Ungenauigkeiten auf:
— | Bei der Eingabe der Daten, da von der unendlichen Menge der reellen Zahlen auf die endliche Menge der Maschinenzahlen abgebildet werden muß. |
— | Beim Rechnen mit den Daten, da die Ergebnisse der einzelnen Operationen selbst meist keine Maschinenzahlen sind und daher nur mit einem gewissen Fehler abgespeichert werden können. |
— | Die sogenannten Verfahrensfehler treten vor allem bei komplizierteren mathematischen Funktionen auf, z.B. Iterationen zur Berechnung von Sinuswerten oder Exponentialfunktionen. |
Die Ursachen dieser Fehler und ihre Fortpflanzung bei der weiteren Verarbeitung sind ein wichtiger Aspekt der Numerik.
Man sagt von einem Algorithmus, daß er „numerisch stabil" ist, wenn man für die Rechenfehler stets eine obere Schranke angeben kann, oder, noch besser, wenn sich (speziell bei Iterationen) die Rechenfehler gegenseitig kompensieren.
Viele mathematische Formeln sind aus numerischen Gründen ungeeignet, auf dem Computer berechnet zu werden.
Oft aber genügen schon geringe Umstellungen bzw. Modifikationen, um den Algorithmus numerisch zu stabilisieren.
Periodische und unendliche Brüche
Ein weiteres Problem ist die Darstellung unendlich langer Zahlen:
Die Zahl
1/3 ist ausgeschrieben 0,33333333...
Werden nur endlich viele Stellen gespeichert - sagen wir 6 - so entspricht dies der Zahl 0,333333.
Die Multiplikation mit drei liefert das Ergebnis 0,999999 - während das exakte Ergebnis natürlich Eins ist.
Hier hat der Mensch eindeutig Vorteile, indem er Brüche wie
1/3 oder Ausdrücke wie √2, π, e
4 usw. nicht auswertet, sondern symbolisch verarbeitet.
Um sin(π) auszurechnen braucht niemand einen Taschenrechner, man weiß eben, das ergibt Null.
Der Computer jedoch kann niemals exakte Ergebnisse liefern, da er schon die Zahl π nicht genau darstellen kann.
Und weil Computer nicht im Dezimal- sondern im Binärsystem arbeiten, tauchen für den Laien manchmal unerwartete Fehler auf:
Bereits den einfachen Bruch
1/10 kann der Computer nicht exakt darstellen, da sich dafür ein unendlicher Binärbruch ergibt.
Zweierpotenzen hingegen kann er sehr gut darstellen (also 1; 2; 128; 2048 sowie 0,5; 0,25; 0,0625 usw.)
Nach soviel Ungenauigkeiten taucht vielleicht die Frage auf, ob man denn eigentlich überhaupt mit Computern rechnen kann - doch, man kann.
Um die auftretenden Fehler möglichst gering zu halten und das Verhalten der arithmetischen Operationen für alle Computer zu standardisieren, wurde vom
IEEE (Institute of Electrical and Electronical Engineers) die Arbeitsgruppe „
P754" ins Leben gerufen.
Diese hatte zur Aufgabe, einen für alle Computerhersteller und Anwender akzeptablen Vorschlag für die Darstellung, Verarbeitung und Fehlerbehandlung von Fließkommazahlen auszuarbeiten, so daß Kompatibilität zwischen allen Computersystemen und Programmiersprachen erreicht werden kann.
Die ganze Sache ist übrigens kein alter Hut:
erst 1981/1982 wurde die Fließkommadarstellung zur Normung vorgeschlagen.
Mittlerweile hat sich der Standard schon stark verbreitet, so erfüllen ihn z.B. moderne Fließkommaprozessoren wie der 80287/80387 von Intel und 68881/68882 von Motorola in allen Punkten.
IEEE P754
Bereits in mc 4/88
[Dieser Artikel liegt leider nicht vor] wurden einige Hinweise zu den in der Norm getroffenen Festlegungen für die Handhabung von Fließkommazahlen gegeben.
Im Folgenden werden alle Punkte des Vorschlags abgehandelt.
Gerechnet wird im Binärsystem.
Das gilt sowohl für die Mantisse als auch für den Exponenten.
Die Verwendung des Binärsystems ist nicht unbedingt zwingend, man könnte auch mit Dezimal- oder Hexadezimalzahlen arbeiten, jedoch wird das Binärsystem von den Mikroprozessoren am effizientesten unterstützt.
Wie schon erwähnt, legt die IEEE-Spezifikation auch das genaue Format von Fließkommazahlen fest.
Zwei Formate wurden vorgesehen:
- Single-Precision (einfache Genauigkeit)
- Double-Precision (doppelte Genauigkeit)
Bild 2 veranschaulicht die beiden Formate.
Bild 2. Format von Zahlen mit einfacher (single-) und doppelter Genauigkeit (double-precision) |
|
Einfache Genauigkeit arbeitet mit 32 Bit, also 4 Bytes, doppelte Genauigkeit hingegen mit 64 Bit, also mit 8 Bytes.
Ein Teil davon entfällt jeweils auf den Exponenten, ein Vorzeichenbit sowie die Mantisse.
Die Mantisse wird stets als positive Zahl gespeichert, für das Vorzeichen wird ein eigenes Bit verwendet.
Die Mantissen werden also nicht wie Integerzahlen im Zweierkomplement gespeichert, sondern getrennt in Betrag und Vorzeichen.
Der Exponent wiederum wird stets mit einem „Bias" verknüpft.
Dieser Bias ist eine Konstante, die vor der Abspeicherung zum Wert des Exponenten addiert wird.
Dadurch erreicht man, daß das Exponentenfeld einer Fließkommazahl stets eine positive Zahl ist.
Für die betragsmäßig kleinsten Zahlen steht im Exponentenfeld der Wert Null, was die Handhabung des Unterlaufes vereinfacht (eine Fließkommazahl ist dann Null, wenn ihr Exponent und ihre Mantisse Null sind).
Durch die Wahl von Vielfachen von Bytes können die Fließkommazahlen effizient gespeichert und übertragen werden.
Moderne 32-Bit-Prozessoren können eine Zahl einfacher Genauigkeit sogar in CPU-Registern verarbeiten.
Das 32-Bit-Format ist ein Kompromiß zwischen Genauigkeit und Rechengeschwindigkeit zugunsten der Rechengeschwindigkeit, während das 64-Bit-Format vor allem wegen hoher Genauigkeit gewählt wurde.
Beim IEEE-Format werden die Mantissen stets in normalisierter Form abgespeichert.
Jede Zahl hat das in
Tabelle 2 angegebene Format mit einer führenden Eins.
Tabelle 2: Normalisierte Zahlen |
Zahl vor der Normalisierung: Normalisierte Zahl: |
0.00100100101 1.00100101000 |
Exponent: 0 Exponent: -3 |
Verschiebung des Punktes um 3 Stellen nach rechts → Exponent:= Exponent - 3 |
Zahl vor der Normalisierung: Normalisierte Zahl: |
100.100000000 1.00100000000 |
Exponent: 3 Exponent: 5 |
Verschiebung des Punktes um zwei Stellen nach links → Exponent := Exponent + 2 |
|
Dadurch wird der zur Verfügung stehende Platz optimal genutzt, weil Vornullen, die ja eigentlich überflüssig sind, nicht mit abgespeichert werden.
Und da ja alle Zahlen gleich anfangen ist es auch unnötig, die führende 1 mit abzuspeichern, man merkt sie sich nur in Gedanken.
Dadurch erhöht sich die Mantissen-Länge um ein Bit.
Man spricht auch von einer „impliziten" Eins.
Von einer nicht-normalisierten Zahl gelangt man zur normalisierten Zahl, indem man das Komma verschiebt, bis vor dem Komma noch genau eine 1 steht.
Bei jeder Komma-Verschiebung wird der Exponent entsprechend der Verschiebung erhöht bzw. erniedrigt.
Als drittes Format fordert IEEE ein „extended-precision"-Format (erhöhte Genauigkeit).
Dieses Format soll stets zum Rechnen und für Zwischenergebnisse verwendet werden.
Es muß über eine größere Genauigkeit als „Double-Precision" verfügen, soll aber gegenüber diesem Format nicht mehr als den doppelten Speicherplatz benötigen.
So verwendet Motorola in seinem Fließkommaprozessor 68881/68882 ein Format von 80 Bit, 64 Bit für die Mantisse und 16 Bit für den Exponenten.
Durch die Verwendung des „extended"-Formats sollen speziell bei Iterationen die Rechenfehler weiter abgesenkt werden, da Genauigkeitsverluste normalerweise nur in den letzten Bits auftreten, die später bei der Darstellung in einfacher oder doppelter Genauigkeit ohnehin nicht abgespeichert werden.
Für den Anwender soll das Format „extended precision" nicht zugänglich sein;
er darf lediglich die beiden Formate „single" und „double precision" sehen.
Für das genaue Format der erhöhten Genauigkeit wird keine Festlegung getroffen, hier hat der Hersteller bzw. Programmierer die freie Wahl.
Die IEEE-Vorschläge sind so gestaltet, daß eine Implementation wahlweise nur in Software, nur in Hardware oder auch in Mischformen erfolgen kann.
Rundung
Das IEEE fordert für alle arithmetische Funktionen eine so hohe Rechengenauigkeit, daß das ermittelte Ergebnis um höchstens die halbe Wertigkeit des letzten Bits der Mantisse vom korrekten Ergebnis abweicht.
Um das zu erreichen, müssen Zwischenergebnisse mit mindestens 1 Bit mehr berechnet werden, als für die Darstellung von Zahlen einfacher bzw. doppelter Genauigkeit benötigt wird.
IEEE fordert sogar 3 Bit, deren Bedeutung
Bild 3 zeigt.
Bild 3. Zusätzliche Bits für Rundungszwecke |
|
Das Guard-Bit (Wächter-Bit) wird berechnet, um eine eventuell nötige Normalisierung der Zahl um ein Bit nach links zu ermöglichen.
Das Round-Bit wird zum Auf- bzw. Abrunden benötigt.
Das Sticky-Bit schließlich ist ein logisches Oder von allen Bits, die im internen Format rechts hinter dem Round-Bit liegen.
Das Sticky-Bit ist wichtig beim Runden für Intervallarithmetik.
Im einzelnen sollen in einer Implementierung nach IEEE die folgenden Rundungsarten und -genauigkeiten enthalten sein.
a) |
default rounding mode (round to nearest):
Zur nächsten Zahl auf- bzw. abrunden.
Der dabei entstehende Fehler ist ±½ LSB (LSB = „least significant bit", also das Bit mit der niedrigsten Wertigkeit in der Mantisse).
|
b) |
directed-rounding-mode:
Vom Anwender bestimmbar wird wahlweise immer aufgerundet (zur nächstgrößeren Zahl) oder immer abgerundet (zur nächstkleineren Zahl).
Der hierbei auftretende Fehler liegt bei maximal ±1 LSB.
|
Die Rundungsgenauigkeit soll immer dem jeweiligen Format entsprechen, also 32 oder 64 Bit.
Zusätzlich soll die Möglichkeit bestehen, eine 64-Bit-Zahl auf 32-Bit zu runden, um sie später in einfacher Genauigkeit weiter zu verarbeiten.
Operationen
Die IEEE-Spezifikation legt nur die grundsätzlichen arithmetischen Operationen fest:
Addition, Subtraktion, Multiplikation, Division, Ziehen der Quadratwurzel, Rest- und Vergleichsoperationen.
Höhere mathematische Funktionen (wie z.B. sin, cos, e-Funktion, log) bauen auf diesen Basisfunktionen auf.
Sie müssen ohnehin durch iterative Verfahren berechnet werden, im Gegensatz zu den Basisfunktionen, die gewissermaßen geradeaus („straight-forward") programmiert werden.
Alle arithmetischen Basisfunktionen müssen den angegebenen Regeln der Rundung gehorchen.
Die Quadratwurzel wurde hier eigentlich nur erwähnt, um eine Ausnahme festzulegen:
√-0 = -0
Normalerweise ist die Wurzel aus einer negativen Zahl undefiniert.
Da jedoch in der IEEE-Spezifikation die Möglichket besteht, auch der Null ein Vorzeichen zu geben, gilt diese zusätzliche Regel.
Die negative Null spielt vor allem beim affinen Abschluß der Zahlen eine Rolle.
Beim projektiven Abschluß darf die negative Null nicht erzeugt werden.
Auch das muß von der Rundungs-Software berücksichtigt werden.
Der Unterschied zwischen projektivem und affinem Abschluß der reellen Zahlen wurde bereits in mc 4/88 (S. 49)
[Dieser Artikel liegt leider nicht vor] gut erklärt.
Format-Konvertierungen
Vom Fließkommapaket müssen folgende Konvertierungen unterstützt werden:
a) |
single precision → double precision (einfache → doppelte Genauigkeit)
|
d) |
floating point → integer (Gleitkomma → ganzzahlig)
|
b) |
double precision → single precision (doppelte → einfache Genauigkeit)
|
e) |
decimal string → floating point (Zeichenkette → Gleitkomma)
|
c) |
integer → floating point (ganzzahlig → Gleitkomma)
|
f) |
floating point → decimal string (Gleitkomma → Zeichenkette)
|
Gerade die letzte Operation gestaltet sich bei der Realisierung extrem aufwendig.
Zusätzlich werden für die Funktionen (e) und (f) die Mindestanforderungen an Zahlenbereiche und String-Längen festgelegt, die bei der Umwandlung verarbeitet werden sollen (Tabelle 3).
Tabelle 3: Anforderungen an Zahlenbereich und Stringlänge für Zahlenkonvertierungsalgorithmen in Arithmetikpaketen |
Betrachten wir nun zur Erholung von soviel Theorie einige Beispiele (Tabelle 4):
Hier werden jeweils zwei Zahlen addiert.
Anhand der Kommentare lassen sich die einzelnen Schritte bei der Fließkomma-Addition nachvollziehen.
NaN's und unendlich...
Werden zwei sehr große positive Zahlen addiert, so kann es zum Überlauf kommen.
Für diesen Fall sieht die IEEE-Spezifikation die Darstellung des Wertes „+∞" (positiv unendlich) vor, analog gibt es den Wert „-∞".
Auch bei der Division durch Null wird +∞ bzw. -∞ erzeugt.
Die Abkürzung NaN steht für „not a number", also „keine Zahl".
Werden Operationen mit unzulässigen Argumenten aufgerufen (etwa eine Wurzel mit negativem Argument), dann erzeugen sie als Ergebnis eine NaN.
Dabei werden zwei NaNs unterschieden:
„signalling" (= trapping) und „nonsignal-ling" (= nontrapping) NaN.
Erstere löst einen Interrupt (sogenannte Exception) aus, während die zweite das nicht tut.
Man nennt die zweite NaN auch „quiet NaN".
Welche der beiden NaNs jeweils erzeugt wird, kann der Anwender über die globale Betriebsart des Fließkommapaketes entscheiden.
Bei der Verwendung von nonsignalling-NaNs müssen an geeigneten Stellen Abfragen durchgeführt werden, die das Auftreten von NaNs erkennen.
Dazu gibt es die Abfragebedingung „unordered" (nicht vergleichbar).
Signalling NaNs hingegen lösen bei ihrer Erzeugung einen Interrupt aus.
In einer entsprechenden Interruptbehandlungsroutine kann dieser vom Anwendungsprogramm abgefangen und ausgewertet werden.
So ergibt sich beispielsweise die Möglichkeit, eine Wurzel aus negativen Zahlen (zum Rechnen mit komplexen Zahlen) in der Interruptroutine zu emulieren.
Um die Werte +∞ und -∞ sowie die NaNs darzustellen, hat man den Zahlenbereich geringfügig eingeschränkt, indem der Exponent $FF bzw. $7FF als Kennzeichnung für diese besonderen Zahlen dient.
Nicht-normalisierte Zahlen
Bei der Diskussion des Zahlenstrahles in Bild 1 fiel die unschöne Lücke im Bereich der Null auf.
Der sogenannte „flush-to-zero"-Effekt, der auftritt, wenn Ergebnisse zu nahe an der Null liegen, kann fatale Folgen haben (z.B. bei anschließender Division).
Die IEEE-Spezifikation sieht aus diesem Grund neben den normalisierten Zahlen, bei denen vor dem Komma immer eine 1 steht, auch denormalisierte (oder nicht-normalisierte) Zahlen vor.
Bei diesen steht vor dem Komma statt einer Eins eine Null.
Zur Kennzeichnung, daß eine Zahl denormalisiert ist, dient wiederum das Exponentenfeld:
Der Wert $00 wird benutzt, um denormalisierte Zahlen zu kennzeichnen.
Tabelle 5 schließlich gibt einen Überblick über die Zahlenbereiche, Darstellungsformate und den Aufbau der Fließkommazahlen mit einfacher und doppelter Genauigkeit.
Tabelle 5: Gleitkomma-Format bei einfacher und doppelter Genauigkeit |
Durch die Einführung der denormalisierten Zahlen werden bei einfacher Genauigkeit nochmals 23 Binärstellen gewonnen, bei doppelter 52 Stellen.
Natürlich sind die denormalisierten Zahlen mit größeren Fehlern behaftet als die normalisierten, da die signifikanten (aussagekräftigen) Bits in der Mantisse relativ weit hinten stehen.
Man spricht jetzt von graduellem Unterlauf, wenn das Ergebnis einer Operation eine denormalisierte Zahl ist.
Endgültiger Unterlauf tritt erst dann auf, wenn die gesamte Mantisse Null ist.
Bild 4 zeigt im Vergleich zu Bild 1 den Gewinn an zusätzlichen Zahlen in der Nähe der Null.
Bild 4. Erweiterung der Fließkommazahlen mit denomalisierten Zahlen |
|
Ausnahmen (Exceptions)
Exceptions sind Interrupts, die von der Fließkomma-Einheit ausgelöst werden, sei diese Einheit ein Softwarepaket oder ein eigener Prozessor.
Die Interrupts signalisieren jeweils Fehler einer bestimmten Klasse.
Jede Klasse verfügt über eine eigene Interruptmaske und kann wahlweise freigegeben oder abgeschaltet werden.
Es werden 5 Klassen unterschieden:
- Invalid Operation: Unzulässiger Befehl oder unzulässiges Rechenergebnis.
- Division by Zero: Division durch Null
- Overflow: Überlauf
- Underflow: Unterlauf
- Inexact: Ungenaues Rechenergebnis
Letzteres tritt auf, wenn beim Runden Bits abgeschnitten wurden.
Eine Erläuterung der genauen 5 Rahmenbedingungen und Sonderfälle für Erzeugung und Behandlung dieser Interrupts würde hier zu weit führen.
Von Interesse sind zunächst nur die verschiedenen Klassen und die Tatsache, daß jede Klasse einzeln ein- und ausgeschaltet werden kann.
Bei abgeschalteten Klassen wird mit den Werten +∞, -∞ sowie den NaNs weitergerechnet.
Die IEEE-Spezifikation sieht daher auch für alle Basisfunktionen entsprechende Algorithmen für die Verknüpfung dieser speziellen Operanden vor (wie die Summe von +∞ und -∞, 3.456 + NaN usw.).
Fortsetzung in Ausgabe 11/88. Sie erhalten die nächste Ausgabe am 24. Oktober.
Eingescanned von
Werner Cirsovius
September 2004
© Franzis' Verlag