TAN (ROM-Routine)

Aus C64-Wiki
Zur Navigation springenZur Suche springen

Anmerkung: Dieser Artikel beschreibt die numerische TAN-Routine im BASIC-ROM.

Name: TAN
Beschreibung: Tangens des Fließkommaregisters FAC berechnen
Einsprungpunkt: $E2B4 / 58036
Übergebene Argumente:
Rückgabe-Werte:


TAN[1] — manchmal auch als TANG[2] bezeichnet — berechnet den Tangens des im Fließkommaregister FAC gespeicherten Winkels. Der Winkel muss im Bogenmaß angegeben werden.

Nach dem Aufruf steht in FAC der Wert der trigonometrischen Funktion, während der Inhalt von ARG undefiniert ist. Wenn TAN mit einem Winkel aufgerufen wird, für den COS den Wert 0 zurückliefert, so meldet TAN einen ?DIVISION BY ZERO ERROR.

Neben dem Inhalt der Fließkommaregister FAC und ARG ändert TAN auch den Hilfszeiger an Adresse 34/$22 (Low-Byte) und 35/$23 (High-Byte) und das Hilfsregister an den Adressen 38/$26 bis 41/$29, die Fließkommaregister FAC#3 und FAC#4 an den Adressen 87/$57 bis 96/$60 und das Tangens-Vorzeichenflag an Adresse 18/$12.

Algorithmus

TAN implementiert keine eigenständige Berechnung des Tangens, sondern wendet nur nacheinander die Routine SIN sowie eine modifizierte Form von COS auf den in FAC übergebenen Winkel an und bildet den Tangens dann als Quotienten dieser beiden Zwischenergebnisse. Die ROM-Routine TAN basiert also auf der Definition tan(x) = sin(x) / cos(x).

  1. Da sowohl SIN als auch COS den übergebenen Winkel mit dem Ergebnis ihrer Berechnungen überschreiben, muss vor dem Aufruf von SIN zunächst dieser Winkel zwischengespeichert werden, damit er später für COS noch zur Verfügung steht. Genau dies erledigt TAN auch im ersten Schritt — verwendet als Zwischenspeicher allerdings das Fließkommaregister FAC#3 an den Adressen 87/$57 bis 91/$5B, das ebenfalls von der innerhalb von SIN aufgerufenen ROM-Routine POLY2 verwendet wird. Das Retten des Winkels nach FAC3 ist damit überflüssig, da der gesicherte Wert dort ohnehin im nächsten Schritt überschrieben wird.
  2. Anschließend wird die ROM-Routine SIN aufgerufen, um den Sinus des Winkels zu ermitteln. Dieser wird zur späteren Verwendung an Adresse 78/$4E bis 82/$52 zwischengespeichert. Als Seiteneffekt setzt SIN das Fließkommaregister FAC#3 auf den Wert Winkel / 2π.
  3. Als nächstes wird dieser Wert aus FAC#3 in FAC übertragen. Ein Aufrufen der ROM-Routine COS würde nun aber ein falsches Ergebnis liefern, da COS in FAC den Winkel erwartet — dort steht allerdings der Wert Winkel / 2π. Um dennoch korrekt den Tangens zu berechnen, wird COS nicht direkt aufgerufen, sondern TAN springt an eine Stelle innerhalb von SIN, umgeht damit die erneute Division durch und addiert stattdessen direkt die Konstante 0.25. Die sich ergebende Summe Winkel / 2π + 0.25 wird an das Approximationspolynom von SIN übergeben und berechnet hierauf basierend den Cosinus des ursprünglichen Winkels. Damit das Vorzeichen von TAN korrekt berechnet wird, obwohl eine modifzierte Form der ROM-Routine COS zum Einsatz kommt, wird ein Tangens-Vorzeichenflag an Adresse 18/$12 mitgeführt.
  4. Zuletzt ruft TAN noch FDIV auf, wobei als Speicherort des Dividenden die Adresse 78/$4E — also ein Zeiger auf den im zweiten Schritt berechneten Sinus — übergeben wird. Diese Division berechnet den Tangens als Quotienten von Sinus und Cosinus. Falls COS zuvor einen Wert von 0 zurückgeliefert hat, so meldet FDIV (und damit TAN) hier einen ?DIVISION BY ZERO ERROR. Auch bei umfangreichen Testreihen ist dagegen nie der Fall aufgetreten, dass der Wert von COS betragsmäßig klein genug war, um bei der Division einen Überlauf auszulösen.

Laufzeitverhalten

Der von TAN mitverwendete Algorithmus sowie das Laufzeitverhalten sind ausführlich in der Beschreibung der ROM-Routine SIN dokumentiert. Weil TAN aber sowohl SIN als auch COS aufruft, müssen die dort angegebenen Laufzeiten verdoppelt sowie um die Rechenzeit für das Addieren von π/2 zum FAC innerhalb von COS und für die Division sin(x) / cos(x) erhöht werden.

Für den Sonderfall TAN(0) ergibt sich eine Laufzeit von 22237 Systemtakten. Für einen von 0 verschiedenen Winkel lag die Laufzeit der TAN-Routine im Rahmen umfangreicher Laufzeitmessungen zwischen 45056 und 50123 Systemtakten.
Ein Systemtakt entspricht auf dem Commodore 64 rund einer Mikrosekunde (μs). Im ungünstigsten Fall benötigt die TAN-Routine also etwas mehr als 50 Millisekunden (ms).

Bugs

  • Wie bereits im ersten Schritt des Algorithmus kurz skizziert, sichert TAN den in FAC übergebenen Winkel vor dem Aufruf von SIN in einen Puffer, der anschließend aber von SIN überschrieben wird. Die darauffolgende Berechnung von COS durch einen Sprung an eine Adresse innerhalb von SIN wirkt eher wie ein zufällig entdeckter Workaround denn wie eine wohlüberlegte Vorgehensweise. Auf die Tatsache, dass das Umkopieren des Winkels zu Beginn von TAN überflüssig ist, weist keines der als Quelle herangezogenen kommentierten ROM-Listings hin[3][4].
  • Dadurch, dass der Cosinus innerhalb von TAN über eine modifizierte Routine berechnet wird, liefert TAN(X) in einigen Fällen einen anderen Wert als die explizite Berechnung SIN(X) / COS(X). Insbesondere lässt sich der Fehler ?DIVISION BY ZERO ERROR nicht dadurch sicher abfangen, dass vor dem Aufruf von TAN auf "COS(X)<>0" geprüft wird:
X=-2740773284/134217728

READY.
?COS(X),SIN(X)/COS(X),TAN(X)
-5.85167232E-09      170891319

?DIVISION BY ZERO  ERROR
READY.


Weblinks

Quellen