16-Bit- von 32-Bit-Arithmetik unterscheiden

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