1

If we have a number in a register and we need to get its decimal digits values - it's easy. All we need is divide the number by 10 and save the remainders we get.

lp:     cmp     eax, 0  ; have we finished?
        je      prinrt
        inc     ecx
        div     dword [divider]
        mov     [result + ecx - 1], dl
        xor     edx, edx
        jmp     lp

But what do I do if a number is just too big for one register? Suppose that there was an instruction mul ebx and the final number is too big to fit 32 bits. Now the result number is written in edx:eax. So what should I do in a situation like this?

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Fyodor
  • 25
  • 5
  • You can use 64-bit to 32-bit divide in two steps to divide a 64-bit pair of dwords to a 64-bit result. It's the same principle as what I used to divide a 32-bit number to a 32-bit result using only the 8086 (32 to 16 bit) `div`, for example in https://hg.pushbx.org/ecm/ldebug/file/c6fe22bcd4f8/source/lineio.asm#l315 Initial `edx` is zero, `eax` gets high dword of number, divide, use `eax` as high dword of result, use remainder in `edx` as high dword input for second `div` with the low dword of number in `eax`. – ecm Mar 19 '23 at 09:42
  • Here's a simpler example without a loop: https://hg.pushbx.org/ecm/ldebug/file/c6fe22bcd4f8/source/expr.asm#l1677 Again this translates to divide the zero-extended high half of input, store result as high half of result, use division remainder as high half for next division with the low half from the input, store result as low half of result, and use the last remainder as the actual remainder of the entire operation. – ecm Mar 19 '23 at 09:50
  • Though coded for 68ik, the same principles apply, see https://stackoverflow.com/a/75631439/471129 – Erik Eidt Mar 19 '23 at 14:24

1 Answers1

2

Conversion of the unsigned 64-bit number held in EDX:EAX

On x86 a cascade of 2 divisions is needed to divide the 64-bit value in EDX:EAX by 10.
The 1st division divides the high dividend (extended with 0) yielding a high quotient. The 2nd division divides the low dividend (extended with the remainder from the 1st division) yielding the low quotient. It's the remainder from the 2nd division that we save on the stack.

To check if the qword in EDX:EAX is zero, I've OR-ed both halves in a scratch register.

Instead of counting the digits, requiring a register, I chose to put a sentinel on the stack. Because this sentinel gets a value (10) that no digit can ever have ([0,9]), it nicely allows to determine when the storage loop has to stop.

    mov     edi, Buffer    ; Begin of the buffer
    mov     ebx, 10        ; CONST
    push    ebx            ; Sentinel
.a: mov     ecx, eax       ; Temporarily store LowDividend in ECX
    mov     eax, edx       ; First divide the HighDividend
    xor     edx, edx       ; Setup for division EDX:EAX / EBX
    div     ebx            ; -> EAX is HighQuotient, Remainder is re-used
    xchg    eax, ecx       ; Temporarily move it to ECX restoring LowDividend
    div     ebx            ; -> EAX is LowQuotient, Remainder EDX=[0,9]
    push    edx            ; (1) Save remainder for now
    mov     edx, ecx       ; Build true 64-bit quotient in EDX:EAX
    or      ecx, eax       ; Is the true 64-bit quotient zero?
    jnz     .a             ; No, use as next dividend

    pop     eax            ; (1a) First pop (Is digit for sure)
.b: add     eax, "0"       ; Turn into character [0,9] -> ["0","9"]
    stosb                  ; Store in buffer
    pop     eax            ; (1b) All remaining pops
    cmp     eax, ebx       ; Was it the sentinel?
    jb      .b             ; Not yet

An alternative to the above that doesn't use the stack:

    mov     edi, Buffer+32 ; End of the buffer
    mov     ebx, 10        ; CONST
.c: mov     ecx, eax       ; Temporarily store LowDividend in ECX
    mov     eax, edx       ; First divide the HighDividend
    xor     edx, edx       ; Setup for division EDX:EAX / EBX
    div     ebx            ; -> EAX is HighQuotient, Remainder is re-used
    xchg    eax, ecx       ; Temporarily move it to ECX restoring LowDividend
    div     ebx            ; -> EAX is LowQuotient, Remainder EDX=[0,9]
    dec     edi
    add     edx, "0"       ; Turn into character [0,9] -> ["0","9"]
    mov     [edi], dl
    mov     edx, ecx       ; Build true 64-bit quotient in EDX:EAX
    or      ecx, eax       ; Is the true 64-bit quotient zero?
    jnz     .c             ; No, use as next dividend

; EDI now points at the left-most digit of the 64-bit number.

Conversion of the signed 64-bit number held in EDX:EAX

The procedure is as follows:

First find out if the signed number is negative by testing the sign bit.
If it is, then negate the number and output a "-" character.

The rest of the snippet is the same as for an unsigned number.

    mov     edi, Buffer    ; Begin of the buffer
    test    edx, edx       ; Sign bit is bit 31 of high dword
    jns     .u             ; It's a positive number
    neg     edx            ; |
    neg     eax            ; | Negate EDX:EAX
    sbb     edx, 0         ; |
    mov     byte [edi], "-"
    inc     edi
.u: mov     ebx, 10        ; CONST
    push    ebx            ; Sentinel
.a:
    ...

Again an alternative that doesn't use the stack:

    test    edx, edx       ; Sign bit is bit 31 of high dword
    pushf                  ; (***)
    jns     .u             ; It's a positive number
    neg     edx            ; |
    neg     eax            ; | Negate EDX:EAX
    sbb     edx, 0         ; |
.u: mov     edi, Buffer+32 ; End of the buffer
    mov     ebx, 10        ; CONST
.c:
    ...

    popf                   ; (***)
    jns     .z             ; It's a positive number
    dec     edi
    mov     byte [edi], "-"
.z:

; EDI now points at the left-most digit (or the sign) of the 64-bit number.
Sep Roland
  • 33,889
  • 7
  • 43
  • 76