Search code examples
assemblyx86nasminteger-division

Unexpected results from division depending on whether I use EAX, AX, or AL


Well, I have a limited (input just one-digit summands) summation program that prints the result of summation with the help of holding remainders in the stack and printing them after that.

  1 global _start
  2 section .data
  3 NEW_LINE            db  10
  4 NEW_LINE_LENGTH     equ $-NEW_LINE
  5 EXIT_SUCCESS_CODE   equ 0
  6 STDIN               equ 0
  7 STDOUT              equ 1
  8 STDERR              equ 2
  9 SYSCALL_EXIT        equ 1
 10 SYSCALL_READ        equ 3
 11 SYSCALL_WRITE       equ 4
 12 
 13 section .bss
 14 sum                 resb 4
 15 remainder           resb 4
 16 buffer              resb 120
 17 
 18 section .text
 19 print_digit: 
 20     mov eax, SYSCALL_WRITE  ; 4
 21     mov ebx, STDOUT         ; 1
 22     int 80h
 23     ret 
 24 _start: 
 25     mov esi, buffer         ; buffer adress --> ESI
 26 again:
 27     ; input number:
 28     mov eax, SYSCALL_READ   ; 3
 29     mov ebx, STDIN          ; 0
 30     mov ecx, esi            ; buffer adress --> ecx
 31     mov edx, 1              ; length
 32     int 80h
 33     cmp byte [esi], 0       ; EOF ?
 34     je end_input
 35     inc esi                 ; next byte of buffer memory
 36     jmp again
 37 end_input:
 38     mov esi, buffer         ; buffer adress --> ESI
 39     xor edi, edi
 40     ; EDI will hold sum:
 41 loop:
 42     cmp byte [esi], 0       ; EOF ?
 43     je div_preparation      
 44     cmp byte [esi], 10      ; line feed ?
 45     je .next_digit
 46     sub byte [esi], 48      ; get value of digit
 47     add edi, [esi]          ; add to summ
 48     add byte [esi], 48
 49 .next_digit:
 50     inc esi                 ; next symbol(?) in buffer memory
 51     jmp loop
 52     ; EDI holds summ now
 53 div_preparation:
 54     mov [sum], edi
 55     xor edx, edx
 56     xor eax, eax
 57     xor ebp, ebp
 58     mov al, [sum]           ; ??????????????????
 59     mov esi, 10
 60 push_remainder:
 61     div esi
 62     push edx                ; push remainder
 63     xor edx, edx            
 64     inc ebp                 ; remainders counter
 65     test eax, eax           ; if the quotient equal to 0 (cmp eax, 0)
 66     jne push_remainder
 67 pop_remainder:
 68     xor eax, eax
 69     test ebp, ebp           ; if remainders counter equals to 0
 70     je quit
 71     pop eax
 72     mov [remainder], eax
 73     add byte [remainder], 48; get digit
 74     mov ecx, remainder
 75     mov edx, 4
 76     call print_digit
 77     dec ebp
 78     jmp pop_remainder
 79 quit:

Why do I get wrong division results while using EAX or AX registers on line 58, but with AL results are correct?


Solution

  • In this part of the code:

     46     sub byte [esi], 48      ; get value of digit
     47     add edi, [esi]          ; add to summ
    

    It looks like the intent is to add a single digit to the sum, but that is not what actually happens. add edi, [esi] is implicitly the same thing as add edi, dword [esi], so really 4 characters are being added to the sum in an interesting way, though only the bottom one is intended, and only one of them is adjusted from an ASCII char to its numeric equivalent (by the way I don't quite understand why you change them back to ASCII characters, but that should be fine in principle, just redundant).

    Using only the low byte of the result works, because the low byte is never "polluted" that way, the extraneous characters are added into the high bytes of eax.

    How to fix: make sure you add only one char at the time. For example

    movzx eax, byte [esi]
    sub eax, 48
    add edi, eax
    

    If you prefer, you can merge the sub and add into lea:

    movzx eax, byte [esi]
    lea edi, [edi + eax - 48]