Search code examples
assemblyyasm

Converting decimal values for output in assembler


I am working on 64-bit assembly code (yasm), trying to output arbitrarily large decimal values so that they read correctly, and not as weirdness, or as a single digit. I had a look around but can't seem to find any suitable solutions online. My code currently outputs a 2-value decimal figure (output quotient, output remainder when divided by 10). For larger values I was thinking along the lines of a do-while loop construction that would repeatedly divide by 10 and output the remainder while the remainder is not zero. Problem with this method is that it would output the result in reverse, adding additional complexity to the code to try and reverse this. Does anyone know of a solution that would work? I had a look at aaa, aam etc but I don't entirely get how they work and suspect these are available to 32-bit operations only.

The code structure is as follows:

section .data   
  nl db "",2,0
  nlf db "",10,0
  input db '',2,0  
  fact dq 1
  y db 10
  store dq 0
  rem dq 0
  quot dq 0
  check dq 0

section .text
  global _start      

_start:         

 ; reading input value 
  mov rax, 0        ; system read
  mov rdi, 0        ; STD IN
  mov rsi, input    ; address first byte in output
  mov rdx, 1        ; load length into rdx
  syscall

  ; reading newline
  mov rax, 0        ; system read
  mov rdi, 0        ; STD IN
  mov rsi, nl       ; address first byte in output
  mov rdx, 1        ; load length into rdx
  syscall

  mov rbx,  [input]     ; for calculating factorial 
  sub rbx, '0'  
  call  calc_fact
  call de_ASCII_fy

  ;add   rax, 30h   

  mov rax, 1        
  mov rdi, 1        
  mov rsi, nlf      
  mov rdx, 1        
  syscall 

  ; exit
  xor rdi, rdi
  push 0x3c
  pop rax
  syscall

  calc_fact:
      cmp   bl, 1
      jg    do_calculation
      mov   rax, 1
      ret

  do_calculation:
      dec   bl
      call  calc_fact
      inc   bl
      mul   bl        
      ret

  de_ASCII_fy:
      mov  [fact], rax
      movzx rax, byte [fact]
      cmp rax, 0
      je decimal_loop
      movzx rbx, byte [y]
      xor rdx, rdx
      div rbx
      xor rcx, rcx
      mov rcx, rdx ; store remainder
      add rax, '0'
      add rdx, '0'
      mov [rem], rdx
      mov [quot], rax
      cmp rcx, 0
      jnz full_print
            mov rax, 1      ; system write
            mov rdi, 1      
            mov rsi, rem    
            mov rdx, 1
            syscall
            ret
            full_print:
            mov rax, 1      ; system write
            mov rdi, 1      
            mov rsi, quot   
            mov rdx, 1      
            syscall
            mov rax, 1      ; system write
            mov rdi, 1      
            mov rsi, rem    
            mov rdx, 1
            syscall
            jmp endif
      endif:ret
      decimal_loop:
      ret

I am calculating a factorial value, trying to display the output for 4! as 24 and 5! as 120. Now, at the moment I can only get to show two decimal values (for some reason the first part of the full_print condition gets skipped, so 3! is printed as 06 instead of 6), but 24 gets printed correctly. I have been racking my brain for a simple way to print out a 3-digit decimal value, but it starts to get very messy with conditionals.


Solution

  • Take a look at this example:

    global _start
    
    section .bss
        decimal resb 32
    
    section .data
    
        number dq 10000000000000000000
        lf db 10
    
    section .text
    
    _start:
        mov rdi, decimal
        mov rsi, [number]
        call IntegerToDecimal
    
        mov   eax, 1        ; sys_write
        mov   edi, 1        ; STDOUT
        mov   rsi, decimal  ; String address
        mov   edx, 32       ; Max. string length
        syscall
    
        mov   eax, 1        ; sys_write
        mov   edi, 1        ; STDOUT
        mov   rsi, lf       ; Line Feed address
        mov   edx, 1        ; Max. string length
        syscall
    
        mov eax, 60         ; sys_exit
        xor edi, edi        ; return 0 (success)
        syscall
    
    IntegerToDecimal:
        mov rax, rsi
        mov ebx, 10         ; Divisor
        xor ecx, ecx        ; RCX=0 (Anzahl der Ziffern)
      .Loop_1:
        xor edx, edx
        div rbx             ; RDX:RAX / RBX = RAX Remainder RDX
        push dx             ; LIFO
        add cl, 1
        or  rax, rax        ; RAX == 0?
        jnz .Loop_1         ; no: once more
      .Loop_2:
        pop ax              ; Get back pushed digits
        or al, 00110000b    ; Convert to ASCII
        mov [rdi], al       ; Store character
        add rdi, 1          ; Increment target address
        loop .Loop_2        ; Until there are no digits left
        mov byte [rdi], 0   ; ASCIIZ-null-terminator
    
        ret
    

    BTW: You had got the example yesterday, if I had known the OS (Windows or Linux) and assembly style (Nasm or Gas). ;-)