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
    Mnemonik

    mul r1
    Beschreibung

    Der 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


Log in to reply