FAQ der Newsgroup de.comp.lang.assembler (d.c.l.a.)

Wie wandle ich eine Dezimalzahl (String) in ein Integer (Byte, Word etc.) um?

In einem Text befinden sich Zahlen als eine Aneinanderreihung von Buchstaben. Damit der Prozessor damit rechnen kann, müssen diese Buchstaben erst in ein vom Prozessor unterstütztes Format umgerechnet werden. Das Intel-Manual spricht hier allgemein von 'Integer', wobei die Größe und das Vorzeichen der Zahl keine Rolle spielen. Wenn man nur eine einzelne Dezimalziffer vor sich hat, ist es kein größeres Problem, sie von der Buchstabenform (ASCII) in die Integerform (binär) zu überführen: Man kappt das obere Nibble. Eine mehrstelige Dezimalzahl in das Integerformat zu überführen, ist allerdings etwas komplizierter, da der Prozessor binär zählt und mit dem Dezimalsystem nichts anfangen kann.

Beispielstring: "29282"

Beginnen wir mit der ersten Ziffer dieser Dezimalzahl, die als einzelner Buchstabe (8 Bit) vorliegt. Nun entspricht eine '2' als Buchstabe leider nicht einer 2 als Integer. Sie muss nach folgender Tabelle umgerechnet werden:

ZahlASCII-Code (binär)Integer (binär)
00011 00000000 0000
10011 00010000 0001
20011 00100000 0010
30011 00110000 0011
40011 01000000 0100
50011 01010000 0101
60011 01100000 0110
70011 01110000 0111
80011 10000000 1000
90011 10010000 1001

Wie man sieht, unterscheiden sich ASCII-Code und Integer nur im vorderen Nibble (4-Bit-Block). Es genügt also, das vordere Nibble des ASCII-Codes (0011) in 0000 umzutauschen, und schon hat man ein Integer. Man braucht noch nicht einmal umzutauschen, sondern kann die ersten 4 Bits einfach löschen:

	; int(2) = chr(2) & 00001111
	mov bl, 00110010b           ; ASCII-Code für '2'
	and bl, 00001111b           ; vorderes Nibble löschen, hinteres Nibble unverändert

Vor der Umwandlung muss man allerdings nachprüfen, ob eine Zahl oder irgend etwas anderes vorliegt. "Davor" deshalb, weil danach auf jeden Fall ein Integer vorliegt, auch wenn das ursprüngliche Zeichen ein Doppelpunkt war.

	; jmp if (bl < '0') || (bl > '9')
	cmp bl, '0'                 ; der Compiler soll das in 00110000b umrechnen
	jb Ausstieg                 ; jmp if bl < '0'
	cmp bl, '9'                 ; der Compiler soll das in 00111001b umrechnen
	ja Ausstieg                 ; jmp if bl > '0'

Zum Dritten muss man sich vor Augen halten, dass die Ziffern einer Zahl eine gewisse Wertigkeit haben. So bedeutet die erste 2 in 29282 nicht nur 2, sondern 20000, die zweite 2 bedeutet 200, während die dritte 2 tatsächlich nur 2 bedeutet. Die Zahl 29282 ist also nur die Abkürzung für 2*10000 + 9*1000 + 2*100 + 8*10 + 2*1. Eine Schwierigkeit besteht darin, dass man normalerweise nicht weiß, wieviel Ziffern die Dezimalzahl hat, und deshalb die Wertigkeit der gerade zu behandelnden Ziffer unbekannt ist. Die Lösung dieses Problems nennt sich Horner-Schema und verläuft für die Zahl 29282 so:

1. Schritt: Man nehme die erste Ziffer und wandle sie in eine Integerzahl um: ax = 2
2. Schritt: Man multipliziere die Zahl mit 10 und addiere die (umgewandelte) zweite Ziffer: ax = (ax*10) + 9 = 29
3. Schritt: Man multipliziere die Zahl mit 10 und addiere die (umgewandelte) dritte Ziffer: ax = (ax*10) + 2 = 292
4. Schritt: Man multipliziere die Zahl mit 10 und addiere die (umgewandelte) vierte Ziffer: ax = (ax*10) + 8 = 2928
5. Schritt: Man multipliziere die Zahl mit 10 und addiere die (umgewandelte) fünfte Ziffer: ax = (ax*10) + 2 = 29282

Wie man sieht, unterscheiden sich Schritte 2 bis 4 nur darin, dass unterschiedliche Ziffern umgewandelt und addiert werden. Man kann diese Schritte für jede beliebige Anzahl von Ziffern wiederholen. Wenn man vor Schritt 1 noch AX mit Null initialisiert, so kann auch der erste Schritt nach diesem Schema gestaltet werden - und man hat die Grundlage für eine Schleife.

In Assembler sieht das dann so aus:

Assembler (Nasm)
BITS 32
SEGMENT. text
Dez2Int:                           ; Übergabe ESI: Zeiger auf ASCIIZ-String mit Dezimalzahl
        xor eax, eax               ; Ergebnis
        xor ebx, ebx               ; BL nimmt die ASCII-Ziffer auf
        mov edi, 10                ; Multiplikator (Basis 10)
  .Schleife:
        mov bl, byte [esi]         ; ASCII-Buchstaben holen
        add esi, 1                 ; Zeiger auf nächste Ziffer
        cmp bl, '0'                ; der Compiler soll das in 00110000b umrechnen
        jb .Ausstieg               ; Sprung wenn bl < '0'
        cmp bl, '9'                ; der Compiler soll das in 00111001b umrechnen
        ja .Ausstieg               ; Sprung wenn bl > '9'
        and bl, 00001111b          ; ASCII nach Integer
        mul edi                    ; Horner-Schema
        add eax, ebx
        jmp .Schleife
  .Ausstieg:
        ret                        ; Ergebnis EAX: Integer

Danach steht die Zahl als Integer in EAX. Eine Fehlerprüfung, ob die Zahl zu groß ist, sprich: das Ergebnis die Grenzen der Register sprengt, kann man an die Stelle des 'jmp Schleife' einbauen, z. B.:

        adc edx, 0                 ; EDX ist der Übertrag der MUL-Operation
        or edx, edx
        jz Schleife
        xor eax, eax
  .Ausstieg:                       ; wie oben

Danach steht in EAX Null, wenn die Horner-Operation einen Überlauf verursacht hat.

Eine verwegene Lösung, mit der man Eindruck schinden kann:

Assembler (Nasm)
BITS 32
SEGMENT .text
Dez2Int:                           ; Übergabe ESI: Zeiger auf ASCIIZ-String mit Dezimalzahl
        xor   eax, eax
    .Schleife:
        movzx ebx, byte [esi]      ; ASCII-Buchstaben holen
        lea   eax, [eax*5]         ; EAX = EAX * 5
        lea   eax, [eax*2+ebx-'0'] ; EAX = EAX * 2 + EBX - 30h
        add   esi, 1               ; nächste Ziffer
        cmp   byte [esi], 0        ; Abschlussnull?
        jne   .Schleife            ; nein
        ret                        ; Ergebnis EAX: Integer

Ralph 'rkhb' Bauer Aug 2008