2

When multiplying 32-bit numbers in assembly, the result will be put in EDX:EAX combination. The upper half of the result goes into EDX and the lower half goes into EAX. If both EDX and EAX have two parts of a result, how can I print these values to the screen using the Irvine32 bit library? See the sample code and comments:

.386
.model flat, stdcall
.stack 4096
ExitProcess proto, dwExitCode:dword
include Irvine32.inc

.data
    num1 dword 1000000
    num2 dword 1000000
    temp dword ?
    full_result qword ?
.code
main proc
    mov eax, num1
    mul num2 ;Result will be put in EDX:EAX (Upper half of number and Lower half of number)
    ;EDX has the value 232 in decimal. 000000E8 in hex
    ;EAX has the value 3567587328 in decimal. D4A51000 in hex
    ;When you put these numbers togather, you get 000000E8D4A51000 in hex. 
    ;When you convert these numbers back to its decimal representation, we get the correct value of 1000000000000

    ;How to display the result into the screen using Irvine32 library (not 64)
    mov temp, eax
    mov eax, edx ;Put the upper half of result in eax
    call WriteDec ;Write the value in eax
    mov eax, temp ;Put the lower half of result in eax
    call WriteDec

    ;This will prints out 2323567587328 instead of 1000000000000

invoke ExitProcess, 0
main endp
end main

screenshot

Is there a way to convert this number 2323567587328 in a different form so that I can display the upper half and lower half correctly? (packed BCD, etc...)

If it is not possible to format this number in a way so that I can have 1000000000000 in two different registers, please let me know how can I assign this value to the full_result qword type variable.

phuclv
  • 37,963
  • 15
  • 156
  • 475
Justin k
  • 1,104
  • 5
  • 22
  • 33
  • If the WriteDec routine takes a 32-bit input then there is no simple composition of calls to it to display a larger number. You will have to look into formatting a number yourself, then use your library's functions to write single digits or strings. – ecm Apr 05 '23 at 17:27
  • 1
    If your Irvine32 library doesn't support 64bit conversion to decimal, you'll have to write your own procedure or macro, such as [StoQD](https://euroassembler.eu/maclib/cpuext32.htm#StoQD). – vitsoft Apr 05 '23 at 19:00
  • 2
    The easy way is to print both halves as hex. Since 16 is a power of 2, base 16 can look at groups of bits separately; the low digit doesn't depend on any higher bits. Otherwise you have to either write your own 64-bit division by 10 loop (or optimize by starting with a division by 10^9 to get to the point where you can use 32-bit operand-size), or call C library functions like `printf` instead of Irvine32 function. – Peter Cordes Apr 05 '23 at 22:59
  • 2
    [Displaying numbers with DOS](https://stackoverflow.com/q/45904075) shows how to handle 32-bit numbers with 16-bit registers. You can do the same thing with 32-bit registers to handle 64-bit integers, storing the decimal digits into a buffer for WriteString. – Peter Cordes Apr 09 '23 at 17:43

1 Answers1

2
mov eax, num1
mul num2

This mul instruction produces an unsigned 64-bit product in EDX:EAX.
What follows is a code that converts the unsigned 64-bit number held in EDX:EAX into its decimal representation. A string that you can then output using Irvine's WriteString function.

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.

.data
    num1   dword 1000000
    num2   dword 1000000
    Buffer byte 32 dup(0)
.code
main proc
    mov     eax, num1
    mul     num2

    push    ebx
    push    edi
    mov     edi, OFFSET 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
    mov     BYTE PTR [edi], 0 ; Irvine32 requires zero-termination
    pop     edi
    pop     ebx

    mov     edx, OFFSET Buffer
    call    WriteString

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, OFFSET 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 PTR [edi], "-"
    inc     edi
.u: mov     ebx, 10        ; CONST
    push    ebx            ; Sentinel
.a:
    ...

The above code snippets are based on my 16-bit Q/A Displaying numbers with DOS. You could read that too for some additional explanations...


Alternative approach for when you don't care about the string always starting at the same known address

This version is shorter and faster.

.data
    num1   dword 1000000
    num2   dword 1000000
    Buffer byte 32 dup(0)
.code
main proc
    mov     eax, num1
    mul     num2

    push    ebx
    push    edi
    mov     edi, OFFSET Buffer+31 ; End of the buffer
    mov     BYTE PTR [edi], 0 ; Irvine32 requires zero-termination

    mov     ebx, 10        ; CONST
.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]
    dec     edi
    add     edx, "0"       ; Turn into character [0,9] -> ["0","9"]
    mov     [edi], dl      ; Store in buffer
    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
    mov     edx, edi       ; -> EDX is address of ASCIIZ string
    pop     edi
    pop     ebx

    call    WriteString

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • 1
    You're going to eventually `call WriteString`, so you could just store backwards from the end of the buffer. No need for push/pop, your loop would finish with a pointer to the most-significant digit wherever it is in the buffer. Also, your `main` function clobbers EBX and EDI without saving/restoring. – Peter Cordes Apr 09 '23 at 18:50
  • Yup, that works. You could also make a buffer on the stack as in [How do I print an integer in Assembly Level Programming without printf from the c library? (itoa, integer to decimal ASCII string)](https://stackoverflow.com/a/46301894). – Peter Cordes Apr 09 '23 at 19:22
  • If actually concerned with performance, you might start with a divide by 1e9, and do high and low halves separately with only a single `div` inside each loop. Or much better, with a multiplicative inverse, as in [Integer-to-ASCII algorithm (x86 assembly)](https://codereview.stackexchange.com/q/142842) on codereview, at least after the first digit of the high half (since the `x / 1000000000` might not fit in 32 bits.) But that's more complicated since the low half needs to pad with zeros to 9 digits if the high half was non-zero. – Peter Cordes Apr 09 '23 at 19:23
  • 1
    Thank you for all of your comments and for explaining to me this in detail. – Justin k Apr 10 '23 at 00:01