| FAQ der Newsgroup de.comp.lang.assembler (d.c.l.a.) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Wie wandle ich ein Integer (Byte, Word etc.) in eine Dezimalzahl (String) um? Wenn der Inhalt eines Registers eine Zahl repräsentiert, dann spricht das Intel-Manual allgemein von 'Integer', wobei die Größe und das Vorzeichen der Zahl keine Rolle spielen. Integer sind immer eine lückenlose Folgen Nullen und Einsen (Bits), die als Binärzahl dargestellt werden können:
In Strings werden die einzelnen Buchstaben durch Bytes (manchmal auch Words) dargestellt. Die Methode, wie diese Bytes zu interpretieren sind, nennt man Zeichensatztabelle. In den verschiedenen, heute gebräuchlichen Zeichensatztabellen stehen die Dezimalziffern immer an derselben Stelle. Allerdings finden sich die Buchstaben '0' bis '9' nicht am Anfang sondern weiter hinten:
Wie man sieht, unterscheiden sich die Bytes in den Tabellen nur im vorderen Nibble. Während das Integer dort eine '0000' aufweist, steht beim Buchstaben eine '0011'. Die Umwandlung einer Integerzahl von 0 bis 9 in einen Buchstaben von 0 bis 9 besteht also darin, das vordere Nibble mit '0011' zu füllen. Hierfür ist der Assembler-Befehl "OR" geeignet.
Aufgabe: Umwandlung der Zahl 5 in den Buchstaben '5'
Assembler (8 Bit):
mov al, 5
or al, 00110000b
Danach steht im Register AL der ASCII-Wert für 5. Wenn die Integerzahl größer 10 ist, kann sie nicht mehr in einen einzelnen Buchstaben umgewandelt werden. Ein oderieren des vorderen Nibbles mit '0011' führt zu einem verwirrenden Ergebnis.
Aufgabe: Umwandlung der Zahl 10 in ASCII
FALSCH:
mov al, 10 ; 10 dezimal = 0000-1010 binär
or al, 00110000b ; ergibt 0011-1010 binär = ASCII ':'
Noch schlimmer wird es bei einer Integerzahl größer 15. Diese sprengt sogar die Nibble-Grenzen, sodass eine Manipulation des vorderen Nibble auch die eigentliche Zahl in Mitleidenschaft zieht.
Aufgabe: Umwandlung der Zahl 26 in ASCII
FALSCH:
mov al, 26 ; 26 dezimal = 0001-1010 binär
or al, 00110000b ; ergibt 0011-1010 binär = ASCII ':'
Danach steht im Register AL der Wert 0011-1011, was wieder dem ASCII-Wert für den Doppelpunkt entspricht. Es gibt keine Möglichkeit mehr herauszufinden, ob der Doppelpunkt nun für 10, 26, 42 oder 58 steht. Man muss also zunächst die Integerzahl in mehrere kleine Einheiten aufteilen, die dann einzeln mit 0011 oderiert werden können. Dies erreicht man durch eine oder mehrere Divisionen durch 10. Die jeweiligen Reste entsprechen dann den einzelnen Ziffern der Dezimalzahl.
Auf diese Weise erhält man die Dezimalziffern von rechts nach links, also in umgekehrter Reihenfolge. In Assembler benötigt man somit einen LIFO-Speicher (LIFO = Last in, first out), damit man die Ziffern später in der richtige Reihenfolge aus dem Speicher holen kann. Es bietet sich dafür ein Stackzugriff mit PUSH an, da ein POP den jeweils letzten per PUSH gespeicherten Wert holt.
Aufgabe: Isolieren und Speichern der einzelnen Ziffern der Integerzahl 29282
Assembler:
mov ax, 29282
mov bx, 10 ; Divisor
xor cx, cx ; CX=0 (Anzahl der Ziffern)
Schleife_1:
xor dx, dx ; DX wird in die Division miteinbezogen
div bx ; DX:AX / BX = AX Rest DX
push dx ; LIFO
add cl, 1 ; ADD soll schneller sein als INC
or ax, ax ; AX = 0?
jnz Schleife_1 ; nein: nochmal
Danach stehen im Stack die Dezimalziffern und in CX die Anzahl der Ziffern, d. h. eine Angabe, wieviel POP's notwendig sind, um alle Ziffern zu erhalten. Nun werden die Ziffern vom Stack geholt, in ASCII umgewandelt und in einen String gespeichert.
Aufgabe: Zurückholen der Ziffern in richtiger Reihenfolge, in ASCII umwandeln und speichern
Assembler:
Schleife_2:
pop ax ; gepushte Ziffern zurückholen
or ax, 00110000b ; Umwandlung in ASCII
stosb ; Nur AL nach [ES:DI] (DI ist ein Zeiger auf den String)
loop Schleife_2 ; bis keine Ziffern mehr da sind
xor al, al
stosb ; ASCIIZ-Abschlussnull nach [ES:DI]
DI bzw. EDI wurde irgendwann vorher mit der Startadresse eines Puffers geladen. Dort steht jetzt ein ASCIIZ-String mit der Dezimalzahl - also eine Reihe von ASCII-Buchstaben mit einer Null als Abschluss. ACHTUNG: Der Stack ist ein empfindliches Medium, in das auch der Prozessor und das Betriebssystem wichtige Daten speichern. Es muss also auf jedes PUSH auch ein POP folgen (nicht mehr und nicht weniger), sonst stürzt das System ab. Schleife_2 darf also erst beendet werden, wenn alle in Schleife_1 gepushten Daten wieder gepopt sind. Hier das Ganze für den 32-Bit-Flat-Modus:
mov eax, 29282
mov ebx, 10 ; Divisor
xor ecx, ecx ; ECX=0 (Anzahl der Ziffern)
Schleife_1:
xor edx, edx
div ebx ; EDX:EAX / EBX = EAX Rest EDX
push dx ; LIFO
add cl,1 ; ADD soll schneller sein als INC
or eax, eax ; AX = 0?
jnz Schleife_1 ; nein: nochmal
Schleife_2:
pop ax ; gepushte Ziffern zurückholen
or al, 00110000b ; Umwandlung in ASCII
stosb ; Nur AL nach [EDI] (EDI ist ein Zeiger auf den String)
loop Schleife_2 ; bis keine Ziffern mehr da sind
mov byte ptr [edi], 0 ; ASCIIZ-Abschlussnull
; (im Flat-Modus wird ES und DS nicht unterschieden)
Ralph 'rkhb' Bauer Aug 2008 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||