The following article was printed in the special issue no. 33 in 1981 of the magazine „Funkschau".
The article describes real numbers and functions based upon B.C.D. design.
Another article describing handling of real numbers may be found here
Luidger Röckrath

Arithmetikroutinen für den 8080

Vergleicht man einfache Mikrocomputer mit Taschenrechnern, so erscheint es als Manko ersterer, daß diese noch nicht einmal rechnen können. Durch entsprechende Programme ist es jedoch möglich, ihnen diese Fähigkeit zu verleihen, wobei sich zeigt, daß sie dies um einiges schneller als ein Taschenrechner bewältigen.

Die im folgenden beschriebenen Arithmetikroutinen für den Mikroprozessor 8080 lehnen sich in einigen Teilen an die in [1] vorgestellten an. Sie verarbeiten Dezimalzahlen mit 12stelliger Mantisse und 2stelligem Exponenten (Bild 1 zeigt das Format). Für die Speicherung jeder Zahl werden 8 Byte benötigt. Die Zahlen werden in einem vom Benutzer definierten Speicherbereich von maximal 2 KByte gespeichert, was 256 Zahlen entspricht. Der Beginn dieses Speicherbereichs muß auf den Anfang einer Page fallen, z. B. 4400. Dann wird die erste Zahl in den Bytes 4400...4407 gespeichert. Jeder dieser 8-Byte-Bereiche für je eine Zahl erhält eine Adresse von 00 bis FF (hexadezimal). Dadurch können sie durch ein CPU-Register adressiert werden.
[Beschreibung des Formates] Bild 1. Format der Zahlendarstellung bei den 8080/Z-80-Rechenroutinen. Jede Zahl besitzt eine zwölfstellige Mantisse und einen zweistelligen Exponenten; im Gegensatz zu vielen binär arbeitenden Basic-Interpretern wird hier dezimal gerechnet
Diese 8 Speicherplätze sollen im folgenden Register genannt werden, sie dürfen aber nicht mit den CPU-Registern verwechselt werden. Die Register 00...03 dürfen nicht benutzt werden, da sie intern Verwendung finden. Werden auch die Routinen zur Berechnung der Wurzel, des Logarithmus, der Exponentialfunktion und der Sinusfunktion benutzt, dürfen zusätzlich die Register 04...0B nicht benutzt werden. Der Beginn des Speicherbereichs zur Abspeicherung der Zahlen muß nur an einer einzigen Stelle im Programm eingesetzt werden und zwar an der Adresse 4C07 (ADL) und 4C08 (ADH).
Nun folgt eine Beschreibung der einzelnen Unterprogramme (Bild 2), wobei mit (D) das durch das CPU-Register D adressierte Register gemeint ist.

4C00
Zu der in L stehenden Registeradresse wird die absolute Adresse erzeugt. Sie steht in HL.
4C17
(L)=(E)          ca. 0,25 ms
(E) wird nach (L) kopiert. (E) bleibt erhalten.
4C88
Die Zahl im Register 00 wird in das normalisierte Format (x,xxx... · 10xx) umgewandelt. Alle folgenden Unterprogramme arbeiten nur mit diesen normalisierten Zahlen und erzeugen solche als Ergebnis.
4D06
(L) = (D)+(E)          ca. 2 ms
(D) und (E) werden addiert, das Ergebnis kommt nach (L). Dabei dürfen zwei oder auch alle drei Register gleich sein, da die eigentliche Rechnung in den Registern 00-03 abläuft (z. B. (5) = (5)+(5)). Die Addition erfolgt nach Angleichung des kleineren Exponenten an den größeren, das Ergebnis wird wieder normalisiert, da eventuell ein Übertrag auftreten kann, und dann nach (L) befördert.
Bild 2. Bytes mit einem vorangestellten Pfeil müssen bei einer Verlegung in einen anderen Adressenbereich geändert werden. An der Adresse 4FE5 muß es korrekterweise 88 statt 7F lauten [In the article the binary screenshot in range 4F00-4F4F was missing. Unfortunately I noted that not until 1989 when I started typing the binary. The publisher sent me the missing part on request. The result of typing and disassembling will be found here]
4CFA
(L)=(D)-(E)          ca. 2.5 ms
Erläuterungen s. o. bei der Addition.
4E09
(L)=(D)x(E)          ca. 7...23 ms
Die Multiplikation erfolgt wie die schriftliche Multiplikation: Stellenverschieben und Addieren. Damit die Rechenzeit möglichst kurz bleibt, sollte die Zahl mit der niedrigeren Quersumme in (E) sein, da dadurch die Anzahl der Additionen, die die Zeit bestimmen, angegeben wird.
4EB8
(L)=(D):(E)          ca. 12...27 ms
Die Divison erfolgt wie schriftliches Dividieren: Stellenverschieben und Subtrahieren.
4F4F
(L)=Konstante          ca. 0,25 ms
Die dem Unterprogrammruf folgenden 8 Bytes werden nach (L) geladen. Danach wird der Programmablauf beim nächsten Byte wieder aufgenommen.
Beispiele:
4800(L)=0ca. 0,25 ms
480C(L)=1ca. 0,25 ms
4818(L)=π=3,14159265359ca. 0,25 ms
4824(L)=e=2,71828182846ca. 0,25 ms
4830(L)=ln10=2,30258509294ca. 0,25 ms
497B
(L)=INT(E)          ca. 0,5 ms
Die Nachkommastellen werden ohne Berücksichtigung des Vorzeichens gleich Null gesetzt.
49AF
(L)=(E)-INT(E)          ca. 3,5 ms
Die Nachkommastellen werden abgetrennt.
487A 4B7A
(L)=ABS(E)          ca. 0,25 ms
(L) ist gleich dem Betrag von (E).
4B8B
(E)-0          ca. 0,1 ms
(E) wird mit 0 verglichen. Ist (E) größer wird das Carry-Flag, ist (E) kleiner als Null das Sign-Flag und ist (E) gleich Null das Zero-Flag gesetzt. (E) wird nicht verändert.
4B84
(D)-(E)          ca. 3 ms
Für die Subtraktion wurde ein spezieller Algorithmus entwickelt, der das 8080-Problem umgeht, daß der DAA nur bei der Addition richtig arbeitet.
483C
(L)=SQR(E)          ca. 200 ms
Die Wurzel wird nach dem Verfahren von Heron ermittelt. Damit man mit wenigen Iterationsschritten auskommt, wird als erster Näherungswert der Exponent von (E) halbiert.
4899
(L)=LOG(E)          ca. 200 ms
Die Wurzel wird nach dem Verfahren von Heron ermittelt. Damit man mit wenigen Iterationsschritten auskommt, wird als erster Näherungswert der Exponent von (E) halbiert.
49BE
(L)=EXP(E)          ca. 120...760 ms
(L) erhält den Wert von e hoch (E). Die Berechnung erfolgt nach der Taylor-Reihe (2).
4AA2
(L)=SIN(E)          ca. 150...600 ms
Die Berechnung erfolgt nach der Taylor-Reihe 2, wobei dafür Sorge getragen wird, daß der Wert zwischen -π/2 und π/2 liegt. Für große Werte (größer 106) wird der Sinus sehr ungenau (übrigens bei Taschenrechnern auch!). Die Eingabe muß in Radianten erfolgen (Bogenmaß).
Für die Verwirklichung weiterer Funktionen noch einige Tips:
log x = ln x/ln 10
10x = ex ln10
cos x = sin (π/2-x)
tan x = sin x/cos x
xy = ex ln x

Ein-/Ausgabe

Das Programm beinhaltet auch eine Ein- bzw. Ausgaberoutine im ASCII-Code, wobei der Punkt als Dezimalkomma dient und E zur Kennzeichnung des Exponenten. Das Pluszeichen wird nicht eingegeben. Der Exponent muß immer zweistellig eingegeben werden, die Mantisse mit max. 8 Dezimalen. Die Eingabe muß mit CR (Return) beendet werden, woraufhin die Zahl normalisiert und in (L) abgespeichert wird. Ansprung der Routine ist 4F6E.
Die Ausgaberoutine gibt (E) aus. Es können nur normalisierte Zahlen ausgegeben werden, und zwar acht Dezimalen, nachdem die neunte gerundet wurde. Die Ansprungadresse ist 4BAB.

Fehlermeldungen

Bei Fehlern wird ein E (ASCII:45) ausgegeben, der SP wird gesetzt und zum Monitor gesprungen.
Fehlerbedingungen:
Über-/Unterlauf (Zahl kleiner 10 -99 oder größer 9,999... · 1099)
Division durch Null.
Wurzel einer negativen Zahl.
Logarithmus einer Zahl kleiner oder gleich Null.

Anpassung an andere Systeme

Das Programm wurde auf einem TRS-80 mit 4KByte RAM mit Hilfe des Monitorprogramms T-BUG entwickelt. Die Anpassung an andere 8080-Systeme ist jedoch äußerst einfach. Das Programm läuft auf dem Adreßbereich 4800...4BAB, als Arbeitsspeicher wird der Bereich 4400-47FF benutzt, d.h. nur die Register 00...7F dürfen benutzt werden. Wie der Arbeitsspeicher auf einen anderen Bereich gesetzt werden kann, wurde oben schon erläutert. Um das Programm in einem anderen Bereich laufen zu lassen, müssen nur alle mit einem Pfeil versehenen Bytes entsprechend geändert werden (siehe Bild 2). Hat man nicht genügend Speicher zur Verfügung, kann man auf die Funktionen Wurzel, Logarithmus, Exponentialfunktion usw. verzichten und nur die Grundrechenarten verwenden; dann benötigt man nur den Programmteil von 4BA2 bis 4FFF, also nur etwas mehr als 1 KByte.

Des weiteren müssen die Ein-/Ausgaberoutinen angepaßt werden. Auf die Adresse 4F7D muß ein Aufruf an eine Eingaberoutine, die ein ASCII-Zeichen vom Keyboard holt, gesetzt werden. Da die Eingaberoutine des TRS-80 bei nicht gedrückter Taste mit gesetzten Zero-Flag zurückkehrt, folgt dem Aufruf ein JZ 4F7D; dieser kann bei andersartigen Eingaberoutinen eventuell entfallen. Die Ausgabe eines ASCII-Zeichen auf den Bildschirm erfolgt beim TRS-80 über RST 2 (D7), dieser steht auf der Adresse 4FFA. Die beiden nächsten Bytes werden durch NOP's belegt, damit bei anderen Systemen dort ein Unterprogrammaufruf eingesetzt werden kann.

An der Adresse 4F5E beginnt die Fehlerroutine; da sie von den verschiedensten Programmteilen angesprungen wird, wird durch den ersten Befehl der Stackpointer auf den Wert 43FD gesetzt. Dies muß bei der Anpassung berücksichtigt werden. Zuletzt muß noch auf die Adresse 4F66 ein Sprung an das entsprechende Monitorprogramm gesetzt werden.

Nach Ausführung dieser Änderungen läuft das Programm auf jedem 8080-, 8085- und Z-80-System. Die oben angegebenen Zeiten beziehen sich auf den 8080 mit 2 MHz; beim 8085 mit 3 MHz verkürzen sie sich um ein Drittel. Ist keine ASCII-codierte Ein-/Ausgabe vorhanden, können die Ein-/Ausgaberoutinen gänzlich umgeschrieben werden. Für die Eingabe steht der Bereich von 4F69-4FEC, und für die Ausgabe 4BA2-4BFF und 4FED-4FFF zur Verfügung.

Literatur

[1] Konz. M.: 6502-Rechenroutinen, FUNKSCHAU 1979, Heft 19, Seite 1125.
[2] Bachmann. H.: Einführung in die Analysis, Band 3: Integrieren-Differenzieren II, Diesterweg Salle, S. 44 ff.

Scanned by Werner Cirsovius
May 2010
© Franzis' Verlag