Assembler-Ecke #14 -------------------- von Tim-Philipp Müller Hallo! Nachdem wir uns letztes Mal durch die Integerarithmetik gequält haben, kommen diesmal die Fließkommaroutinen des OS dran. Vorher allerdings noch ein bischen zum Thema 'Programmierstil'. Unbestritten, jeder hat seine eigene Art, wie er dies und das in Assembler umsetzt. In fast keiner anderen Sprache gibt es soviele Lösungen für das gleiche Problem, die allesamt gleich elegant (bzw. umständlich) sind. Der eine bevorzugt Tabellen, der andere ist eben ein absoluter Unterprogramm- freak... Das Folgende soll deshalb (wie immer) auch nur als 'Denkanstoß' dienen... Kaum ein längeres Programm kommt ohne Unterprogramme (Subroutinen, Subs) aus. Müssen keine Parameter übergeben werden, gibt es damit meist auch keine Probleme. Ohne Schwierigkeit ist es auch zu schaffen, drei Bytes an die Subroutine zu übergeben. Wollen wir mehrere Bytes, z.B. Tabellen, Filenames,etc. übergeben, haben wir nun mehrere Möglichkeiten zur Auswahl: Einmal könnten wir einen Zeiger auf den Anfang der 'Bytefolge' übergeben. Das könnte dann so aussehen: TXT .AT "HALLO!" PRG LDA #TXT LDY /TXT JSR PRINT ... Nun gibt es dazu natürlich noch eine Alternative. Man könnte die Tabelle auch direkt hinter dem JSR einfügen und müßte dann nur sicherstellen, daß beim Rücksprung die Tabelle übergangen wird. Ein typisches Beispiel: PRG JSR PRINT .DA "HALLO!",#$9B NOP ... Besonders bei längeren Programmen ist dies von Vorteil, denn wer nach einem halben Jahr seine Sources nochmal durchschaut oder überarbeitet, weiß meist nicht mehr, was ITXT2S oder DTXT war. Man hat eben die relevanten Daten an der relevanten Stelle... So,wie schreibt man nun aber die entsprechende Unterroutine? Erste Überlegung ist, daß der Compy bei einem JSR die Rücksprungadresse-1 (!) auf den Stack schiebt. Das heißt für uns: Wir holen die Adresse vom Stack, erhöhen das Ganze um eins und haben die Anfangsadresse für unsere Tabelle gewonnen (die kann man ja dann in eine Page-0-Pointer dem eigentlichen Unter- programm zugänglich machen). Nun sucht man das Ende der Tabelle. Dieses wird entweder durch ein festgelegtes Byte gekennzeichnet, das logischerweise in der Tabelle nicht vorkommen darf, oder man bringt am Anfang der Adresse die Länge der Tabelle ein, oder man geht nach dem Motto: 'Erstes Byte = Letztes Byte' (dadurch kann man das Endbyte jeder Tabelle individuell anpassen). Nun schiebt man die Adresse des letzten Bytes der Tabelle auf den Stack zurück (auf den Stack muß ja Rücksprungadr-1), dadurch wird beim RTS die Tabelle über- jumped. Am effektivsten ist es natürlich, wenn man eine einfache Subroutine schreibt, die den Tab-Anfang in einen Pointer legt, und die Rücksprungadresse korrigiert, die am Anfang jedes solchen 'speziellen' Unterprogramms aufgerufen wird. Doch nicht vergessen: Dann liegt auch noch die eigene Rücksprungadresse vor der eigentlichen auf dem Stack! Beispiel: JSR PRINT .DA "HALLO",#$9B JMP .. PRINT JSR SUB ... RTS SUB PLA eigene STA SAVE Rück- PLA sprung STA SAVE+1 adr.!! PLA Rückadr. STA BACK von PLA PRINT STA BACK+1 zu MAIN ... ... ... Soviel dazu. Nun zu den Fließkommaroutinen des OS. Im Prinzip braucht man sie in Assembler nur, wenn man einen eigenen Hochsprach- Compiler schreiben möchte (QUICK kam aber auch ohne aus).... Ansonsten: Für komplizierte Mathe-Aufgaben sollte man vielleicht bei Turbo-Basic, Pascal oder Prolog bleiben. Was man wissen muß, sind eigentlich nur ein paar Routinen und Register. Eine Zahl im Fließkommaformat belegt sechs Bytes, eine im Integerformat (Word) zwei Bytes. FR0 $D4-$D9 Rechenregister 1 FR1 $E0-$E5 Rechenregister 2 CIX $F2 Index zu INBUF INBUFF $F3,$F4 Zeiger auf Zahlen in ATASCII-Strings FLPTR $FC,$FD Zeiger auf Fließkomma- zahl LBUFF $580 Ergebnisspeicher der AFP-Routinen Benutzte Register: $D4-$FF, $57E-$5FF Im allgemeinen dient das Carry-Flag als Indikator für Fehler. Wer FP (Floatpoint)- Routinen verwenden möchte, muß natürlich das OS eingeblen- det haben. Das FP-Pack weicht auch Gerätetreibern am parallelen Bus... (für deren Arbeitszeit) Das Register FR1 wird bei fast allen Aktionen verändert! --- IFP $D9AA Integer -> FP Wandelt Integerwert in FR0 (bzw. ersten zwei Bytes von FR0) in eine Fließkomma- Zahl um und schreibt diese nach FR0. --- FPI $D9D2 FP -> Integer Wandelt FP-Zahl in FR0 in Integerwert um und rundet notfalls. Das Ergebnis steht in den ersten zwei Bytes von FR0. --- AFP $D800 ASCII -> FP Wandelt Zahl im ASCII-Format in FP-Zahl um. INBUFF zeigt dabei auf den Speicher- bereich mit dem umzuwandelnen String, CIX gibt den Index innerhalb dieses Buffers zur Zahl an. Die effektive Adresse ist also INBUFF+CIX. Es werden solange Zeichen umgewandelt, bis ein ungültiges Zeichen auftritt. Erlaubt sind die Zahlen 0-9,+,-,E,und . (der Dezimalpunkt). FR0 erhält die umgewandelte FP-Zahl und CIX zeigt nachher auf das nächste Byte nach dem ASCII-String (z.B. für Zahlentabellen oder -listen sehr nützlich). --- FASC $D8E6 FP -> ATASCII Wandelt FP-Zahl in FR0 in ASCII-String um, der an die Adresse, an die INBUFF zeigt, geschrieben und dessen Ende durch Invertierung gekennzeichnet wird. --- ZFR0 $DA44 LÖSCHT FR0 --- ZF1 $DA46 LÖSCHT ZERO-PAGE-FP-ZAHL Löscht Fließkommaregister in Page 0, auf dessen Adresse das -Register zeigt (Also 6 Byte Länge). --- FSUB $DA60 SUBTRAKTION FR0=FR0-FR1 --- FADD $DA66 ADDITION FR0=FR0+FR1 --- FMUL $DADB MULTIPLIKATION FR0=FR0*FR1 --- FDIV $DB28 DIVISION FR0=FR0/FR1 --- PLYEVL $DD40 POLYNOMEN-Berechnung Berechnet Polynom: Xn*Y^n+X(n-1)*Y^(n-1)....X1*Y+X0 X,Y: Anfangsadresse der Koeffizienten- Liste im FP-Format (X=LO,Y=HI) : Anzahl der Koeffizienten FR0: Wert von Y im FP-Format Das Ergebnis steht in FR0. --- FLD0R $DD89 FR0 Wert zuweisen --- FLD1R $DD98 FR1 Wert zuweisen Kopiert FP-Zahl,auf die und (Hi) zeigen, nach FR0 bzw. FR1. --- FLD0P $DD8D FR0 Wert zuweisen --- FLD1P $DD9C FR1 Wert zuweisen Damit wird FR0 bzw. FR1 der Wert einer beliebigen FP-Zahl zugewiesen, auf die FLPTR zeigt (quasi identisch mit der ROutine FLD0R). --- FST0R $DDA7 FR0 ABSPEICHERN Speichert FR0 in die durch (Hi) und angegebene Adresse ab. --- FST0P $DDAB FR0 ABSPEICHERN Wie FST0R, nur daß die Adresse in FLPTR steht. --- EXP $DDC0 EXPONENTIALFUNKTION Berechnet FR0=e^FR0 --- EXP10 $DDCC EXPONENTIALFUNKTION Berechnet FR0=10^FR0 --- LOG $DECD NATÜRLICHER LOGARITHMUS FR0=LN(FR0) --- LOG10 $DED1 NAT. LOGARITHMUS ZU 10 Berechnet natürlichen Logarithmus zur Basis 10 einer FP-Zahl. FR0=LOG(FR0) So long, Good Byte, nächstes Mal wieder mit etwas weniger Mathematischem...