Probleme bei Umwandlung von ASCII zu Dezimal
-
Hallo,
ich versuche mich gerade an einem minimalen Taschenrechner, der derzeit nur Addition untersützt. Die von stdin gelesenen Zahlen muss ich erstmal in Dezimal und nach der Rechnung wieder in die ASCII-Codierung umwandeln. Nur mach ich wohl was falsch - ergeben 2 Summanden ein Ergebnis welches größer ist als 9, wird die Zahl fehlerhaft dargestellt.
Auch wenn der Fehler vermutlich nur im add-Unterprogramm liegt, hier mal der gesamte Code:
section .text global _start _start: ; print headline mov ecx, calcHeadline mov edx, calcHeadlineLength call print ; prompt first number mov ecx, enterFirstNum mov edx, enterFirstNumLength call print mov ecx, firstNumber mov edx, 4 ; we want at a maximum of 4 bytes call get_input ; prompt second number mov ecx, enterSecondNum mov edx, enterSecondNumLength call print mov ecx, secondNumber mov edx, 4 ; we want at a maximum of 4 bytes call get_input jmp add add: mov eax, [firstNumber] mov ebx, [secondNumber] sub eax, 48 sub ebx, 48 add eax, ebx add eax, 48 mov [result], eax ; print result msg mov ecx, resultMsg mov edx, resultMsgLength call print ; print result mov ecx, result mov edx, 4 call print jmp exit print: mov eax, 4 ; write... mov ebx, 1 ; ...to standard output int 0x80 ret get_input: mov eax, 3 ; read... mov ebx, 0 ; ...from standard input int 0x80 ret exit: ; write new line mov ecx, newLine mov edx, newLineLength mov eax, 4 mov ebx, 1 int 0x80 ; exit mov eax, 1 ; system-exit mov ebx, 0 ; no errors int 0x80 ;=============================================================== ; VARS section .bss firstNumber: resb 4 ; reserve 4 byte secondNumber: resb 4 ; reserve 4 byte result: resb 4 ; reserve 4 byte ;=============================================================== ; DATA section .data newLine db 10 newLineLength equ $ - newLine calcHeadline db 10, '- CALCULATOR -', 10, 0 calcHeadlineLength equ $ - calcHeadline enterFirstNum db 10, 'Geben Sie die erste Zahl ein: ', 0 enterFirstNumLength equ $ - enterFirstNum enterSecondNum db 10, 'Geben Sie die zweite Zahl ein: ', 0 enterSecondNumLength equ $ - enterSecondNum resultMsg db 10, 'Ergebnis: ' resultMsgLength equ $ - resultMsg
Ich verwende NSAM unter Linux. Was mach ich bei der Umstellung genau falsch?
-
Du musst die Ziffern entsprechend ihrer Position mit 10^n ; n=0,1,2... multiplizieren, bevor du sie aufsummierst:
result = 0 foreach char in string { result = result*10 + (char-'0') }
Die Rückwandlung erfolgt klassischerweise durch Division mit Rest:
1234/10 = 123 REST [b]4[/b] 123/10 = 12 REST [b]3[/b] 12/10 = 1 REST [b]2[/b] 1/10 = 0 REST [b]1[/b]
-
Wenn man mit einstelligen Ziffern rechnet, und es kommt eine zweistellige Zahl raus, dann müssen die beiden Ziffern getrennt konvertiert werden.
Mal angenommen man addiert
9 (39h) + 8 (38h) = 17 (also sinnvoll 3137h)
aber hier kann man schon mal durcheinander kommen
-
Danke für eure Antworten. Ich scheitere derzeit leider an dem Unterprogramm string2int, welches die eingegebene Zahlen-Zeichenkette in einen Integer-Wert umwandeln soll.
add: mov esi, firstNumber call string2int cmp eax, 20 je exit ; print result msg mov ecx, resultMsg mov edx, resultMsgLength call print jmp exit string2int: xor eax, eax ; make eax-register empty mov edx, 0xA ; 0xA = 10d call char2int_loop char2int_loop: xor ebx, ebx mov ebx, [esi] ; load actual byte to ebx cmp ebx, 0 ; is the byte the NULL-ASCII-Character? => means end of string je leave ; it was the NULL-Character so leave mul edx sub ebx, 0x30 ; subtract 0x30 (=48 in ASCII) add eax, ebx inc esi ; increment to get next byte jmp char2int_loop ; go on with the next byte leave: ret
Gebe ich die Zahl 20 ein, sollte das add-Unterprogramm die resultMsg nicht mehr ausgeben, was aber nicht der Fall ist - also geht bei der Umwandlung wohl was falsch.
In der ersten Zeile von add weise ich ja die Adresse des ersten Bytes des Strings dem esi-Register zu - oder sieht der Syntax in NASM dafür vll. anders aus?
Bei der mul-Instruktion in char2int_loop habe ich mich an folgender Beschreibung orientiert:MUL
Multiplikation von 32 Bit Registern
Mnemonikmul r1
BeschreibungDer in Register eax enthaltene 32 Bit Wert wird mit dem Inhalt des Register r1 multipliziert und das 64 Bit Ergebnis in Register edx (Bits 32-63) und eax (Bits 0-31) abgelegt.
Den Wert von edx benötige ich ja nicht!? Für die Zahl 20 sind ja gerade mal 5 Bits notwendig, da muss das eax-Register doch langen?
-
Ob du es brauchst oder nicht, der MUL- Befehle gibt dir ein 64Bit Ergebnisse zurück und überschreibet dementsprechend EDX. Du kannst nun entweder die 8Bit-Variante von MUL nehmen oder IMUL benutzen:
IMUL ebx,ebx,10 ; ebx=ebx*10
T!P-TOP schrieb:
mov ebx, [esi] ; load actual byte to ebx
Es werden 4 Bytes geladen! Mit MOVZX kann man einzelne Bytes in ein 32 Bit Register holen.
-
T!P-TOP schrieb:
Ich scheitere derzeit leider an dem Unterprogramm string2int, welches die eingegebene Zahlen-Zeichenkette in einen Integer-Wert umwandeln soll.
Das Unterprogramm verlangt einen ASCIIZ-String, also eine Zeichenkette, die mit Null abschließt. Dein Unterprogramm "get_input" liefert so etwas aber nicht. Der String ist entweder randvoll oder endet mit dem Zeilenendezeichen (LF = 10). Andererseits liefert "get_input" die Anzahl der gelesenen Zeichen zurück, die dann übernommen werden kann oder um 1 vermindert werden muss, wenn das letzte Zeichen ein LF ist:
get_input: mov eax, 3 ; read... mov ebx, 0 ; ...from standard input int 0x80 cmp BYTE[ecx+eax-1], 10 ; LF noch im Puffer? jne .return dec eax .return: ret
Nun liefert get_input in EAX die Anzahl der gelesenen Ziffern zurück. Mit dieser Anzahl kannst Du dann die Umwandlungsroutine füttern:
Dez2Int: ; Übergabe: ; ESI Zeiger auf Dezimalstring ; ECX Anzahl der Ziffern xor eax, eax ; Startwert EAX=0 .L: ; EAX = EAX * 10 = EAX * 8 + EAX * 2 lea edx, [eax*8] ; EDX = EAX * 8 add eax, eax ; EAX = EAX * 2 add eax, edx ; EAX = EAX + EDX movzx ebx, BYTE[esi] ; nächste Ziffer and bl, 0x0F ; ASCII-Ziffer -> Int add eax, ebx inc esi loop .L ret ; Rückgabe: ; EAX Integer
viele grüße
ralph
-
Ich habe nun das Gegenstück von dez2int geschrieben:
int2dez: mov ecx, 4 .int2dez_l: xor edx, edx cmp eax, 0 je .finish mov ebx, 10 div ebx ; the dividend will be stored in eax, the remainder will be stored in edx add edx, 0x30 ; add 48 (=0x30) to remainder to get the ascii-value mov byte[edi], dl dec edi loop .int2dez_l .finish: ret
Funktionieren tuts jedenfalls, nur das muss ja nicht viel heißen Würdet Ihr das anders handhaben?
@ralph: Du hast in deinem Code folgendes verwendet:
cmp BYTE[ecx+eax-1], 10 ; LF noch im Puffer?
In ecx befindet sich ja die Adresse des ersten Bytes des Buffers, in eax die Anzahl an Bytes die eingegeben wurden und mit -1 erhält man dann am Schluss die Adresse des zuletzt eingegebenen Bytes. Nur wie löst ein Assembler das in etwa auf? Wie würde diese Adressen-Berechung aussehen, wenn man Instruktionen verwendet?
Was macht der Assembler eigentlich, wenn er auf das Byte[]-Schlüsselwort trifft?Ich verwende derzeit die MUL-Instruktion, würde aber dennoch gerne wissen, wieso folgender Code das selbe bewirkt wie MUL mit 10d:
lea edx, [eax*8] ; EDX = EAX * 8 add eax, eax ; EAX = EAX * 2 add eax, edx ; EAX = EAX + EDX
Unklar ist dabei vor allem die erste Zeile.
Nochmal vielen Dank für eure Hilfe!
-
T!P-TOP schrieb:
Ich habe nun das Gegenstück von dez2int geschrieben:
...
Funktionieren tuts jedenfalls, nur das muss ja nicht viel heißen Würdet Ihr das anders handhaben?Wenn es funktioniert und Dir gefällt, ist alles in Ordnung. Irgendwann wird Dich wurmen, dass die Dezimalzahl rechtsbündig ausgegeben wird und ihre Maximallänge auch arg beschränkt ist. Das soll Dich aber jetzt nicht stören.
@ralph: Du hast in deinem Code folgendes verwendet:
cmp BYTE[ecx+eax-1], 10 ; LF noch im Puffer?
In ecx befindet sich ja die Adresse des ersten Bytes des Buffers, in eax die Anzahl an Bytes die eingegeben wurden und mit -1 erhält man dann am Schluss die Adresse des zuletzt eingegebenen Bytes. Nur wie löst ein Assembler das in etwa auf?
Das Byte an der errechneten Adresse wird geladen und mit 10 (hexadezimal: 0x0A) verglichen. Das Ergebnis des Vergleichs landet in den Flags, womit dann z. B. ein bedingter Sprungbefehl (JNE) arbeiten kann. Der Assembler generiert einen Maschinensprachebefehl, den der Prozessor später ausführt. Der Prozessor kann nämlich nur Maschinensprache und kein Assembler.
Wie würde diese Adressen-Berechung aussehen, wenn man Instruktionen verwendet?
Zum Beispiel:
mov esi, ecx add esi, eax sub esi, 1 mov al, [esi] ; kein "BYTE" notwendig, hier merkt das der Assembler von allein cmp al, 10
Was macht der Assembler eigentlich, wenn er auf das Byte[]-Schlüsselwort trifft?
Er generiert den entsprechenden Maschinenbefehl, dass der Prozessor nur ein Byte laden und vergleichen soll. Anschaulicher in hexadezimal: cmp BYTE [ecx+eax-1], 0x0A. Bei WORD würde er zwei Byte vergleichen: cmp WORD [ecx+eax-1], 0x000A. Bei DWORD vier Bytes: cmp DWORD [ecx+eax-1], 0x0000000A.
Ich verwende derzeit die MUL-Instruktion, würde aber dennoch gerne wissen, wie folgender Code das selbe bewirkt wie MUL mit 10d:
lea edx, [eax*8] ; EDX = EAX * 8 add eax, eax ; EAX = EAX * 2 add eax, edx ; EAX = EAX + EDX
Unklar ist dabei vor allem die erste Zeile.
Eigentlich ist die Adressenarithmetik dazu da, die Adresse eines Elements in einer Tabelle zu bestimmen. Man kann sie aber auch für ganz einfache Berechnungen heranziehen. Multiplikation geht nur mit 2,4 und 8. Du musst also den Term (EAX*10) umwandeln: (EAX*8) + (EAX*2). Der Befehl LEA ist dazu da, das Ergebnis einer solchen Berechnung in ein Register zu übernehmen. Ein MOV würde sofort den dortigen Speicherinhalt auslesen. Vorteil: Es geht ein bisschen schneller als MUL, es sind nur zwei Register beteiligt und sieht waaahnsinnig schick aus.
viele grüße
ralph
-
Man kann es noch schicker machen:
lea edx,[eax*8] lea eax,[edx+eax*2]
Ein westlichen Vorteil von LEA ist, das man Arithmetik betreiben kann ohne die Flags zu modifizieren.masm