FDIV

Aus C64-Wiki
Zur Navigation springenZur Suche springen

Anmerkung: Dieser Artikel beschreibt die numerische FDIV-Routine im BASIC-ROM, die allen Divisionen im BASIC V2 des Commodore 64 zugrundeliegt.

Name: FDIV
Beschreibung: Fließkommazahl aus dem Speicher nach ARG holen und durch FAC dividieren
Einsprungpunkt: $BB0F / 47887
Übergebene Argumente:
Akkumulator: Adresse Dividend A (Low-Byte)
Y-Register: Adresse Dividend A (High-Byte)
Sonstige: FAC = Divisor B
Rückgabe-Werte:
Sonstige: FAC = Ergebnis der Division A/B, ARG = Dividend A


FDIV — manchmal auch als MEMDIV[1], DIVMF[2] oder M-DIV[3] bezeichnet — holt eine Fließkommazahl aus dem Speicher des C64 in das Fließkommaregister ARG und dividiert sie dann durch den bereits im Fließkommaregister FAC befindlichen Wert. Die Speicheradresse der Fließkommazahl wird im Akkumulator (Low-Byte) und im Y-Register (High-Byte) übergeben. Die Zahl muss im kompakten 5 Byte-Format vorliegen, wie es zum Speichern von REAL-Variablen verwendet wird. Es können sowohl Werte im RAM als auch Konstanten im BASIC-ROM adressiert werden.

Nach dem Aufruf steht in FAC das Ergebnis der Division, während der Inhalt von ARG undefiniert ist. Anders als bei den ROM-Routinen FADD, FSUB und FMULT steht der aus dem Speicher gelesene Operand also nicht für nachfolgende Numerik-Routinen zur Verfügung. Ist der Quotient zu groß für die Fließkommadarstellung des C64, so löst FDIV einen ?OVERFLOW ERROR aus; in diesem Fall ist FAC anschließend undefiniert, während ARG noch den Dividenden aus dem Speicher enthält. Ist der Divisor in FAC gleich 0, so meldet FDIV einen ?DIVISION BY ZERO ERROR; in diesem Fall bleiben beide Operanden in FAC und ARG unverändert.

Neben dem Inhalt der Fließkommaregister FAC und ARG ändert FDIV auch das Hilfsregister an den Adressen 38/$26 bis 42/$2A. Die innerhalb von FDIV aufgerufene CONUPK-Routine überschreibt zudem den Hilfszeiger an den Adressen 34/$22 (Low-Byte) und 35/$23 (High-Byte).

Algorithmus

Die Routine FDIV führt nacheinander die folgenden Schritte aus:

  1. Die Routine CONUPK wird aufgerufen, um die zu dividierende Fließkommazahl aus dem Speicher in das Fließkommaregister ARG zu kopieren. CONUPK vergleicht zudem die Vorzeichen von FAC und ARG und speichert das Ergebnis im höchstwertigen Bit von Adresse 111/$6F (0: Vorzeichen sind gleich, 1: Vorzeichen sind verschieden). Zuletzt lädt CONUPK den Exponenten des Fließkommaregisters FAC in den Akkumulator und setzt dadurch das Zero-Flag genau dann, wenn FAC gleich 0 ist. Diese beiden Schritte sind Voraussetzungen für die nachfolgende Division.
  2. Falls das Zero-Flag gesetzt ist, der Divisor in FAC also gleich 0 ist, so löst FDIV einen ?DIVISION BY ZERO ERROR aus.
  3. Ist das Zero-Flag dagegen gelöscht und somit FAC ungleich 0, so bereitet FDIV die Division vor, indem es die Mantisse von FAC rundet und den Exponenten von FAC negiert. Im nächsten Schritt verarbeitet FDIV dann die Exponenten der beiden Operanden in gleicher Weise wie FMULT. Stellt sich dabei heraus, dass der Exponent von ARG und damit auch ARG selbst den Wert 0 hat, so wird die Berechnung abgebrochen und stattdessen der Wert 0 nach FAC kopiert.
  4. In allen anderen Fällen berechnet FDIV einen vorläufigen Wert für den Exponenten des Produkts, indem es die Exponenten des Dividenden in ARG und den (negierten) Exponenten des Divisors in FAC addiert und die Summe dann um den Exzess korrigiert. Tritt bei dieser Berechnung ein Überlauf auf, so bedeutet dies, dass der Quotient von ARG und FAC auf dem C64 voraussichtlich nicht als Fließkommazahl darstellbar sein wird. FDIV löst daher in diesem Fall einen ?OVERFLOW ERROR aus. Ergibt sich bei der Berechnung des vorläufigen Exponenten dagegen ein Unterlauf, so wird der Betrag des Quotienten sicherlich kleiner sein als der betragsmäßig kleinste Wert; in diesem Fall wird FAC einfach auf 0 gesetzt.
  5. Andernfalls wird das von CONUPK vorbereitete Ergebnis des Vorzeichenvergleichs von Adresse 111/$6F als neues Vorzeichen von FAC übernommen (die Division zweier Zahlen mit gleichem Vorzeichen führt zu einem positiven, von Zahlen mit unterschiedlichem Vorzeichen zu einem negativen Resultat).
  6. Nach diesen Vorbereitungen und Überprüfungen kann die eigentliche Division durchgeführt werden. Das Hilfsregister an den Adressen 38/$26 bis 42/$2A füllt sich hierbei nach und nach mit dem Quotienten der Mantissen von ARG und FAC. FAC wird wiederholt mit dem aktuellen Wert von ARG verglichen; ist ARG größer oder gleich, so wird FAC von ARG subtrahiert und ein gesetztes Bit an den Quotienten angehängt, ansonsten bleibt ARG unverändert und der Quotient wird um ein gelöschtes Bit verlängert. Nach jedem solchen Schritt aus Registervergleich und eventueller Subtraktion wird der Inhalt von ARG um ein Bit nach links verschoben. Auf diese Weise wird sukzessive ein 34 Bit langer Quotient berechnet (32 Bit Mantisse plus die zwei höchstwertigen Bits im Rundungsbyte).
  7. Im letzten Schritt wird schließlich der Inhalt des Hilfsregisters in die Mantisse von FAC kopiert und falls nötig linksbündig gemacht.

Laufzeitverhalten

  • In jedem Fall wird zunächst die zu dividierende Zahl aus dem Speicher geholt (Routine CONUPK, typischerweise 85 Systemtakte).
  • Falls ARG gleich 0 ist, so wird dies bei der Verarbeitung der Exponenten erkannt. In diesem Fall setzt FDIV nur noch den Wert von FAC auf 0 und das Vorzeichen auf "positiv" (plus 80 Takte).
  • Ist stattdessen ARG ungleich 0, so kann die Subtraktion der Exponenten zu einem Unterlauf führen. In diesem Fall lässt sich das Ergebnis der Division auf dem C64 nicht darstellen und wird stattdessen auf 0 gesetzt (plus 90 Takte).
  • In allen anderen Fällen wird die Division durchgeführt. Die dafür benötigte Rechenzeit wird bestimmt durch die Anzahl der gesetzten Bits in der Mantisse des Quotienten. Die kürzeste Laufzeit ergibt sich, wenn der Quotient eine Zweierpotenz ist (nur ein einziges gesetztes Bit im Quotienten, 1678 Takte). Bei lauter gesetzten Bits in der Mantisse von FAC steigt die Rechenzeit für die Division auf bis zu 3259 Systemtakte. Falls der Quotient der Mantissen kleiner als 0.5 ist, so erhöht sich die Laufzeit um weitere 30 Systemtakte für das anschließende Normalisieren (Linksbündig machen durch Linksverschieben).

Ein Systemtakt entspricht auf dem Commodore 64 rund einer Mikrosekunde (μs). Im ungünstigsten Fall benötigt die FDIV-Routine also für eine Division etwas mehr als 3 Millisekunden (ms).

Bugs

FDIV berechnet im 4. Schritt des obigen Algorithmus den vorläufigen Exponenten des Resultats und prüft nur anhand dieses Zwischenwerts (und ohne Berücksichtigung der Mantissen), ob die Division zu einem Überlauf führen könnte. Dadurch löst FDIV unter Umständen einen ?OVERFLOW ERROR aus, obwohl sich der Quotient von ARG und FAC durchaus im Fließkommaformat darstellen ließe. Ein Beispiel hierfür ist die Division "9E+37 / 0.9", die einen Überlauf meldet, obwohl das Ergebnis 1E+38 vom C64 verarbeitet werden könnte. Dagegen liefert die Berechnung "7E+37 / 0.5" korrekt das deutlich größere Resultat 1.4E+38.

Um frühzeitig zu erkennen, dass der Betrag des Produkts zu klein für die Fließkommadarstellung des C64 sein wird, prüft der Algorithmus im 4. Schritt nicht nur auf einen Unterlauf des Exponenten, sondern auch auf einen Exponenten von 0. Wird dieser Sonderfall erkannt, so setzt FDIV allerdings nicht einfach das Resultat in FAC auf 0, sondern springt stattdessen in eine Routine, die lediglich das Vorzeichen von FAC auf "positiv" setzt. Ergibt die nachfolgende Division dann einen Quotienten, der auf dem C64 noch darstellbar ist, so ist dessen Wert fälschlicherweise immer positiv:

?-5E-39,-5E-39/1
-5.00000001E-39      5.00000001E-39


Weblinks

Quellen