FMULT

Aus C64-Wiki
Zur Navigation springenZur Suche springen

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

Name: FMULT
Beschreibung: Fließkommazahl aus dem Speicher nach ARG holen und FAC mit dieser Zahl multiplizieren
Einsprungpunkt: $BA28 / 47656
Übergebene Argumente:
Akkumulator: Adresse Faktor A (Low-Byte)
Y-Register: Adresse Faktor A (High-Byte)
Sonstige: FAC = Faktor B
Rückgabe-Werte:
Sonstige: FAC = Ergebnis der Multiplikation A*B, ARG = Faktor A


FMULT — manchmal auch als MEMMULT[1][2], M-MULT[3] oder einfach als Multiplikation[4] bezeichnet — holt eine Fließkommazahl aus dem Speicher des C64 in das Fließkommaregister ARG und multipliziert dann den bereits im Fließkommaregister FAC befindlichen Wert mit dieser Zahl. 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 Multiplikation, während ARG weiterhin die aus dem Speicher gelesene Zahl enthält. Diese kann dann für nachfolgend aufgerufene Numerik-Routinen weiter verwendet werden. Ist das Produkt zu groß für die Fließkommadarstellung des C64, so löst FMULT einen ?OVERFLOW ERROR aus. In diesem Fall ist FAC anschließend undefiniert, während ARG noch den Summanden aus dem Speicher enthält.

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

Algorithmus

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

  1. Die Routine CONUPK wird aufgerufen, um die zu multiplizierende 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 Multiplikation.
  2. Falls das Zero-Flag gesetzt ist, FAC also gleich 0 ist, so wird sich daran durch eine Multiplikation mit ARG nichts ändern. Weitere Berechnungen sind somit nicht erforderlich und FMULT wird beendet..
  3. Ist das Zero-Flag dagegen gelöscht und somit FAC ungleich 0, so verarbeitet FMULT im nächsten Schritt die Exponenten der beiden Faktoren. 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 FMULT einen vorläufigen Wert für den Exponenten des Produkts, indem es die Exponenten der beiden Faktoren addiert und die Summe dann um den Exzess korrigiert. Tritt bei dieser Berechnung ein Überlauf auf, so bedeutet dies, dass das Produkt von FAC und ARG auf dem C64 voraussichtlich nicht als Fließkommazahl darstellbar sein wird. FMULT 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 Produkts 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 Multiplikation von Faktoren mit gleichem Vorzeichen führt zu einem positiven, von Faktoren mit unterschiedlichem Vorzeichen zu einem negativen Resultat).
  6. Nach diesen Vorbereitungen und Überprüfungen kann die eigentliche Multiplikation durchgeführt werden. Das Hilfsregister an den Adressen 38/$26 bis 41/$29 speichert hierbei die Summe der Teilprodukte. Das Fließkommaregister FAC wird byteweise gelesen (einschließlich des Rundungsbytes an Adresse 112/$70). Falls hierbei ein Nullbyte gelesen wird, so kann die Berechnung beschleunigt werden, indem der Inhalt des Hilfsregisters in einem Schritt um ein Byte (8 Bit) nach rechts verschoben wird. Ansonsten wird in 8 einzelnen Durchgängen die Summe der Teilprodukte jeweils um ein Bit nach rechts verschoben und dabei für jedes gesetzte Bit in FAC der Wert von ARG zum aktuellen Wert hinzuaddiert.
  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 multiplizierende Zahl aus dem Speicher geholt (Routine CONUPK, typischerweise 85 Systemtakte).
  • Falls FAC gleich 0 ist, so ist keine Berechnung erforderlich und FMULT kann sofort verlassen werden (plus 17 Takte).
  • Falls dagegen ARG gleich 0 ist, so wird dies erst bei der Verarbeitung der Exponenten erkannt. In diesem Fall setzt FMULT nur noch den Wert von FAC auf 0 und das Vorzeichen auf "positiv" (plus 46 Takte).
  • Sind stattdessen sowohl FAC als auch ARG ungleich 0, so kann die Addition der Exponenten zu einem Unterlauf führen. In diesem Fall lässt sich das Ergebnis der Multiplikation auf dem C64 nicht darstellen und wird stattdessen auf 0 gesetzt (plus 56 Takte).
  • In allen anderen Fällen wird die Multiplikation durchgeführt. Die dafür benötigte Rechenzeit wird bestimmt durch die Anzahl und die Position der gesetzten Bits in der Mantisse von FAC. Sie lässt sich berechnen als
    1716 - 195 x (Anzahl der Nullbytes in der Mantisse von FAC) + 37 x (Anzahl der gesetzten Bits in der Mantisse von FAC),
    wobei zu beachten ist, das zu dieser Mantisse als fünftes Byte auch das Rundungsbyte an Adresse 112/$70 gehört. Falls das Produkt der Mantissen kleiner als 0.5 ist, so sind zu dem so ermittelten Wert noch 30 Systemtakte für das anschließende Normalisieren (Linksbündig machen durch Linksverschieben) hinzuzurechnen. Die kürzeste Laufzeit ergibt sich, wenn FAC eine Zweierpotenz ist (nur ein einziges gesetztes Bit in der Mantisse, 1716 - 195 x 4 + 37 x 1 + 30 = 1003 Takte). Bei lauter gesetzten Bits in der Mantisse von FAC steigt die Rechenzeit für die Multiplikation auf 2735 Systemtakte; falls zudem das Rundungsbyte an Adresse 112/$70 durch eine vorausgehende Berechnung auf einen von Null verschiedenen Wert gesetzt wurde, so kann sich die Rechenzeit sogar auf maximal 3226 Takte erhöhen (jeweils einschließlich Normalisierung).

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

Bugs

FMULT 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 Multiplikation zu einem Überlauf führen könnte. Dadurch löst FMULT unter Umständen einen ?OVERFLOW ERROR aus, obwohl sich das Produkt von FAC und ARG durchaus im Fließkommaformat darstellen ließe. Ein Beispiel hierfür ist die Multiplikation "8.6E+37 * 1", die einen Überlauf meldet, obwohl das Ergebnis 8.6E+37 vom C64 verarbeitet werden könnte. Dagegen liefert die Berechnung "8.5E+37 * 1.9" korrekt das deutlich größere Resultat 1.615E+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 FMULT allerdings nicht einfach das Resultat in FAC auf 0, sondern springt stattdessen in eine Routine, die lediglich das Vorzeichen von FAC auf "positiv" setzt und dann zur Multiplikation zurückkehrt. Daraufhin werden unnötigerweise die Mantissen von FAC und ARG miteinander multipliziert und das Ergebnis in die Matisse von FAC kopiert. Bei allen darauffolgenden Rechenschritten wird dieser Inhalt dann aber ignoriert, da der Exponent von 0 einen Wert von 0 kennzeichnet.

Für das byteweise Verschieben des Hilfsregisters im 6. Schritt des obigen Algorithmus wird eine Routine verwendet, die eigentlich für das Angleichen von Exponenten im Rahmen der Addition entwickelt wurde und die das Hilfsregister um eine beliebige Anzahl Bits verschieben kann. Wird diese zum byteweisen Verschieben missbraucht, so ist dies nicht nur unnötig aufwändig, sondern funktioniert auch nur, wenn die Routine mit gesetztem Carry-Flag aufgerufen wird. Enthält die Mantisse von FAC aber zwei aufeinanderfolgende Nullbytes, so ist diese Voraussetzung nicht mehr gegeben und das Hilfsregister wird anstatt um 8 sogar um 9 Bits nach rechts verschoben:

?16777217*1,1*16777217
 16777217            16777216.5


Weblinks

Quellen