Assembler-Ecke #13 -------------------- von Tim-Philipp Müller ACHTUNG: Folgende Passagen in MATHE.INC müssen gestrichen werden: ZIN / TOP1 / TOP2 / GELDREST . Diese sind nicht vorhanden. Hallo! Diesmal soll sich alles um Zahlen und Rechnen in Maschinensprache drehen. Wer seinen Assembler mit etwas Arithmetik quälen möchte, hat im allgemeinen zwei Möglichkeiten: - Rechnen mit Words (Integerzahlen) Bereich: 0-65535 bzw. -32767 bis 32767 - Rechnen mit rationalen Zahlen (Fließkommazahlen) Zuerst die Integerarithmetik. Hier können wir entweder alle 16 Bits benutzen, was einen Zahlenbereich von 0-65535 ermöglicht ('unsigned'), oder auch nur 15 Bits für die Zahl und das höchste Bit für das Vorzeichen -positiv oder negativ- was uns einen Spielraum von -32767 bis 32767 schafft (signed). Ein Über-/Unterschreiten des Zahlen- bereichs wird in den folgenden Routinen nicht geprüft. Die Variablennamen sind zur besseren Übersicht klein geschrieben, im Source- code muß man sie natürlich in 'Groß- buchstaben' (Kapitales) schreiben. Addition und Subtraktion müssten ja eigentlich noch bekannt sein, doch hier nochmal -der Vollständigkeit wegen: SUM1 + SUM2 = SUMME CLC LDA sum1 ADC sum2 STA summe LDA sum1+1 ADC sum2+1 STA summe+1 SUM1 - SUM2 = DIFF SEC LDA sum1 SBC sum2 STA diff LDA sum1+1 SBC sum2+1 STA diff+1 FAK1 * FAK2 = PRODUKT So, nun bräuchte man einen Algorithmus zur Multiplikation... Die einfachste Methode ist natürlich eine Schleife, die einfach FAK1-mal FAK2 zu PRODUKT addiert, nachdem dieses am Anfang auf 0 gesetzt wurde. Das ist aber nicht allzu schnell, da man im schlechtesten Fall ja 255mal addieren müßte. Mittels der Schiebebefehle ASL und LSR lassen sich Zahlen ja schnell mal zwei nehmen bzw. durch zwei teilen. Der beste mir bekannte Algorithmus ist folgender: PRODUKT wird auf 0 gesetzt. Ist FAK1 oder FAK2 gleich null, sind wir bereits fertig. Wenn nicht, prüfen wir, ob FAK1 ungerade ist. Ist dies der Fall wird FAK2 zu PRODUKT addiert. In jedem Fall wird nun FAK1 durch zwei geteilt (ist fak1 ungerade, wird abgerundet) und gleichzeitig FAK2 verdoppelt. Ist FAK1 immer noch nicht 0, wird die Schleife weiterhin durchlaufen. Als Diagram: PRODUKT=0 FAK2=0? ja-> Ende LOOP: nein: FAK1=0? ja-> Ende nein: FAK1 ungerade? ja: PRODUKT=PRODUKT+FAK2 FAK1=FAK1/2 FAK2=FAK2*2 ->LOOP Als Source: LDA #0 STA produkt STA produkt+1 CMP fak2+1 (* STA fak2+1) BNE .1 (*) CMP fak2 BEQ .99 .1 LDA #0 (*) CMP fak1+1 (*) BNE .2 (*) CMP fak1 (* LDA fak1) BEQ .99 .2 LDA fak1 (*) AND #1 (* .2 AND #1) BEQ .3 CLC LDA produkt ADC fak2 STA produkt LDA produkt+1 ADC fak2+1 STA produkt+1 .3 LSR fak1+1 ROR fak1 (*) ASL fak2 ROL fak2+1 JMP .1 .99 RTS Den Algorithmus kann man natürlich noch mathematisch beweisen, aber das ist hier wohl nicht nötig, oder? Arbeitet man mit 'signed' words (also die mit Vorzeichen), bleibt die Routine gleich, man muß nur noch die beiden Vorzeichenbits exclusiv oderieren (EOR) und das Ergebnis als Vorzeichenbit des Ergebnis setzen. Möchte man nur mit Bytes rechnen, müssen alle mit (*) markierten Zeilen geändert werden (steht dahinter) oder entfallen ganz. Das Ergebnis ist in jedem Fall 16 Bit. DIV1 / DIV2 = QUOTE (Rest in REST) Auch hier greifen wir wieder zu einem ausgeklügelten Algorithmus, der eigentlich genauso funktioniert, wie das schriftliche Dividieren, das jeder Drittkläßler lernt. 1. Zuerst werden QUOTE und REST auf null gesetzt. Dann wird der Divisor (DIV2) linksbündig gemacht, d.h. so lange nach links geschoben, bis das höchste Bit gesetzt ist (Bit 15). Die Anzahl der notwendigen Schiebevorgänge wird gezählt und in der Variablen Z gemerkt. 2. DIV2 wird nun von DIV1 subtrahiert. Ist das Ergebnis kleiner null (also Carry=0), wird ein null-Bit von rechts in QUOTE geschoben, andernfalls schiebt man ein gesetztes Bit rein und benutzt das Resultat als neuen Dividenen DIV1. Anschließend wird der Divisor durch zwei geteilt, die Zählvariable Z dekremiert und die Schleife ab Punkt 2 durchlaufen, bis Z null ist. TEMP=Temporäres Zwischenregister (Word) Hier das Diagram: Z=0, QUOTE=0, REST=0 (A) Schiebe DIV2 nach links Z=Z+1 Bit 15 von DIV2 gesetzt? nö:->(A) (B) TEMP=DIV1-DIV2 C-Flag in QUOTE schieben (von rechts) TEMP<0? ja: DIV1=TEMP Schiebe DIV2 nach rechts Z=Z-1 Z=0? nein: ->(B) ja: REST=DIV1 Hier der Sourcecode: LDA #0 STA quote STA quote+1 STA z CMP div2+1 Divisor=0? BNE .1 ja-> ENDE! CMP div2 BNE .1 RTS .1 BIT div2+1 Bit 7 des Hi-Bytes ist nun im N-Flag BMI .2 INC z Zähler erhöhen ASL div2 Divisor um ein Bit ROL div2+1 nach links schieben JMP .1 und weiter checken.. .2 SEC LDA div1 temp=div1-div2 SBC div2 STA temp LDA div1+1 SBC div2+1 STA temp+1 PHP Flagstatus auf Stack ROL quote ROL quote+1 PLP und wiederherstellen BCC .3 LDA temp STA div1 div1=temp LDA temp+1 STA div1+1 .3 LSR div2+1 div2=div2/2 ROR div2 DEC z Zähler dekremieren BPL .2 schon durch? RTS ja:(REST=div1) ENDE! REST=DIV1 Das war's auch schon. Wer auch hier signed Words verwenden möchte, muß wie bei der Multiplikation die Vorzeichen exclusiv oderieren. Außerdem ist zu beachten, daß statt Bit 7 das Bit 6 abgefragt wird (steht dann im V-Flag, d.h. statt BMI müßte BVS stehen), da die eigentliche Zahl ja ein Bit kürzer ist. Vor der Rechnung müssen die beiden Vorzeichenbits allerdings gelöscht werden!