In einigen Fällen, wie bei SPACE oder CONVERT, kommt es vor, daß beim Umwandeln der Zahl nach den eben beschriebenen Algorithmen selbst die 16-Bit-Arithmetik nicht ausreicht, über die der 8086 standardmäßig verfügt.
Das folgende Programm, eine erweiterte Variante von DEMO3 aus dem Abschnitt »Zahlen anzeigen«, soll z. B. die 32-Bit-Zahl im Registerpaar DX:AX auf dem Bildschirm anzeigen:
A MOV AX,E240 ;100 Anzuzeigende Zahl: 123456 MOV DX,1 ;103 MOV BX,A ;106 Divisor DIV BX ;109 Zahl/10 nach AX, Rest nach DX PUSH DX ;10B Ziffer auf dem Stapel ablegen OR AX,AX ;10C Alle Ziffern bearbeitet? JZ 115 ;10E XOR DX,DX ;110 Nein: MSB löschen CALL 109 ;112 Rekursion! POP DX ;115 Ziffer holen ADD DL,30 ;116 Ziffer in ASCII umwandeln MOV AH,2 ;119 und anzeigen INT 21H ;11B DOS-Funktion: Zeichen anzeigen RET ;11D NDEMO6.COM RCX 1E W Q
Nach dem Aufruf erscheint wie gewünscht die verwendete Zahl 123456 auf dem Bildschirm. Vergrößern Sie jedoch die Zahl beispielsweise auf den Wert 655360, indem Sie die zweite und dritte Zeile des Listings in »MOV AX,0« und »MOV DX,A« abändern, tritt beim Aufruf eine unangenehme Eigenart des 8086 zu Tage: Interrupt 0, Teilerüberlauf – das Programm ist vorzeitig beendet. Beim Versuch, 655360 durch 10 zu teilen, mußte die CPU feststellen, daß ein 16-Bit-Register für das Ergebnis nicht ausreicht, und äußerte auf diese dramatische Art ihren Unmut. Um das zu verhindern, ist eine Divisionsroutine nötig, die auch mit einem 32-Bit-Ergebnis zurecht kommt. Durch einige mathematische Überlegungen läßt sich ein Algorithmus dafür aus nur 7 Standardbefehlen konstruieren.
Dazu stellen wir zunächst den Dividenden in den Registern DX:AX in der mathematisch korrekten Schreibweise als 65536*DX+AX dar. Wenn wir davon ausgehen, daß sich der Divisor im Register BX befindet (16 Bit genügen für unsere Zwecke), sieht die Division folgendermaßen aus:
Ergebnis = (65536*DX+AX)/BX
Die Anwendung des Kommutativgesetzes ergibt den Term
Ergebnis = 65536*DX/BX + AX/BX.
Weil der 8086 die Division DX/BX nur ganzzahlig ausführen kann, führen wir den Rest »R« dieser Operation ein, der im Bereich 0 bis BX-1 liegen kann. Der Ausdruck lautet dann:
Ergebnis = 65536*(DX/BX+R/BX) + AX/BX = 65536*(DX/BX) + 65536*R/BX + AX/BX = 65536*(DX/BX) + (65536*R+AX)/BX
Die Division (65536*R+AX)/BX kann der Prozessor nun problemlos durchführen, weil der Rest R und damit der ganze neue Dividend nie so groß sein kann, daß die Division einen Überlauf erzeugt! Der Rest dieser zweiten Division ist gleichzeitig der Rest der gesamten Operation. Sämtliche Multiplikationen mit 65536 lassen sich natürlich rein durch Register-Verschiebungen bewältigen. Eine Umsetzung der ermittelten Formel finden Sie im folgenden Programm, das eine (nun funktionierende!) Abwandlung von DEMO3.DEB darstellt:
A MOV AX,0 ;100 Anzuzeigende Zahl: 655360 MOV DX,A ;103 MOV BX,A ;106 Divisor ; Hier beginnt die Division: MOV CX,AX ;109 LSB der Zahl sichern MOV AX,DX ;10B DX/BX berechnen XOR DX,DX ;10D DIV BX ;10F DB 91 ;111 XCHG AX,CX: ; MSB-Ergebnis nach CX, LSB nach AX DIV BX ;112 (65536*R+AX)/BX berechnen XCHG DX,CX ;114 Rest nach CX, MSB-Ergebnis nach DX ; Ende der Division PUSH CX ;116 Ziffer auf dem Stapel ablegen MOV CX,DX ;117 Alle Ziffern bearbeitet? OR CX,AX ;119 JZ 120 ;11B CALL 109 ;11D Nein: Rekursion! POP DX ;120 Ziffer holen ADD DL,30 ;121 Ziffer in ASCII umwandeln MOV AH,2 ;124 und anzeigen INT 21H ;126 RET ;128 NDEMO7.COM RCX 29 W Q
Ähnliche Überlegungen lassen sich auch für die Multiplikation anstellen, die vor allem für das Einlesen von 32-Bit-Zahlen wichtig ist. Setzen wir unseren mathematischen Ausflug ein wenig fort. Benötigt wird für den genannten Zweck ein Algorithmus, der eine 32-Bit-Zahl im Registerpaar DX:AX mit einer 16-Bit-Zahl in BX multipliziert. Das Ergebnis muß dabei wieder in DX:AX Platz finden. Mathematisch ausgedrückt lautet die Aufgabe:
Ergebnis = (65536*DX+AX)*BX = 65536*DX*BX + AX*BX
Durch zwei Multiplikationen, deren Ergebnisse geeignet aufaddiert werden, läßt sich auch dieser Algorithmus recht kurz gestalten. Die folgenden 6 Zeilen übernehmen die Aufgabe:
MOV BP,AX ;LSB sichern MOV AX,DX ;MSB multiplizieren: 65536*DX*BX MUL BX DB 95 ;XCHG AX,BP ;MSB-Ergebnis nach BP, LSB nach AX MUL BX ;LSB multiplizieren: AX*BX, DX ist 0 ADD DX,BP ;Ergebnisse addieren
Nun befindet sich das Ergebnis wie gewünscht in DX:AX. Eine gute Zusammenfassung aller hier besprochenen Arithmetikroutinen bietet das Programm CONVERT.DEB aus dem Kapitel »Andere Befehle«. CONVERT liest zunächst zwei Dezimalzahlen in zwei Register ein. Die dritte Zahl wandelt das Programm mithilfe der 32-Bit-Multiplikationsroutine um, wobei es als Multiplikator anstelle der sonst üblichen 10 (Dezimalsystem) die erste Zahl des Parameters verwendet. Das 32-Bit-Ergebnis zeigt CONVERT nun prompt wieder auf dem Bildschirm an, verwendet dafür aber – wen wundert’s – die 32-Bit-Divisionsroutine, diesmal mit der zweiten Zahl statt der 10 als Divisor. Das Ergebnis: CONVERT zeigt die dritte Zahl, die in demjenigen System geschrieben ist, das durch die erste Zahl festgelegt wird, im System der zweiten Zahl an. »CONVERT 10 16 n« wandelt so die Zahl n vom Zehnersystem ins 16er-, sprich Hexadezimalsystem um, ebenso »CONVERT 32 2 n« eine Zahl des ungebräuchlichen 32er-System ins Dualsystem und so fort, eine recht nützliche Angelegenheit.
Wir haben CONVERT deshalb hier nochmals komplett dokumentiert abegdruckt. Wir hoffen, daß wir Ihnen damit einige Anregungen geben konnten, wie Sie Ihre Assemblerprogramme so kurz wie möglich gestalten können. Und auch wenn Sie sich bisher selten mit der Assemblerprogrammierung beschäftigt haben, konnten Sie doch vielleicht einen Einblick gewinnen, was hinter den Kulissen eines unscheinbaren Programmes so alles stecken kann. Vielleicht werden Sie beim genaueren Anschauen eines unserer Tips jetzt schmunzelnd feststellen, daß Sie den dort verwendeten Algorithmus inzwischen kennengelernt haben. Auf jeden Fall sollten Sie sich nicht scheuen, die erläuterten Routinen auch in Ihren eigenen Programmen zu verwenden. Denn ein gut optimierter Algorithmus bereitet doch um einiges mehr an Freude, als eine Routine, die zwar das macht was sie soll, jedoch unnötig lang und undurchsichtig ist. In diesem Sinne…
A MOV BX,81 ;100 Zeiger auf Kommandozeile MOV SI,A ;103 Multiplikator zum Einlesen einer Dezimalzahl CALL 13B ;106 Quellsystem einlesen PUSH AX ;109 und sichern CALL 13B ;10A Zielsystem einlesen MOV DI,AX ;10D Divisor zum Anzeigen der Zahl POP SI ;10F Multiplikator für Quellsystem CALL 13B ;110 Zahl im Quellsystem einlesen ; Ergebnis anzeigen: MOV BP,AX ;113 LSB der Zahl sichern MOV AX,DX ;115 DX/DI berechnen XOR DX,DX ;117 DIV DI ;119 DB 95 ;11B XCHG AX,BP: ; MSB-Ergebnis nach BP, LSB nach AX DIV DI ;11C (65536*R+AX)/DI berechnen XCHG BP,DX ;11E Rest nach BP, MSB-Ergebnis nach DX PUSH BP ;120 Ziffer auf dem Stapel ablegen MOV BP,AX ;121 Alle Ziffern bearbeitet? OR BP,DX ;123 JZ 12A ;125 CALL 113 ;127 Nein: Rekursion! POP DX ;12A Ziffer holen ADD DL,30 ;12B und in ASCII umwandeln CMP DL,39 ;12E >9? JBE 136 ;131 ADD DL,27 ;133 dann "Sprung" von Ziffern auf Buchstaben MOV AH,2 ;136 Ziffer anzeigen INT 21 ;138 RET ;13A XOR DX,DX ;13B Zahl einlesen: XOR AX,AX ;13D Ergebnis löschen XOR CX,CX ;13F Neue Ziffer: 0 vorgeben CMP By[BX],D ;141 Ende des Parameters? JZ 166 ;144 CMP CL,9 ;146 Ziffer>9? JBE 14E ;149 SUB CL,27 ;14B dann "Sprung" berücksichtigen MOV BP,AX ;14E Multiplikation: LSB sichern MOV AX,DX ;150 MSB multiplizieren: 65536*DX*SI MUL SI ;152 DB 95 ;154 XCHG AX,BP ; MSB-Ergebnis nach BP, LSB nach AX MUL SI ;155 LSB multiplizieren: AX*SI, DX ist 0 ADD AX,CX ;157 neue Ziffer und gleichzeitig ADC DX,BP ;159 die Summanden addieren INC BX ;15B Nächstes Zeichen lesen MOV CL,[BX] ;15C OR CL,20 ;15E Kleinschrift SUB CL,30 ;161 Ziffer? JNB 141 ;164 dann weiter einlesen RET ;166 RCX 67 NCONVERT.COM W Q
Quelle: 200 Utilities für PC-/MS-DOS von Gerhard Schild und Thomas Jannot