简体   繁体   中英

Adding 32-bit numbers on a 32 machine, widening to a 64-bit sum in two registers

How to add couple of 32 bit numbers on a 32 bit machine but without precision loss, ie in a 64 bit "pseudo register" eax:edx . Using Intel syntax assembler.

Assuming the 32-bit numbers you're adding are in EAX and EBX, and are unsigned:

    xor edx,edx       ;Set edx to zero
    add eax,ebx
    adc edx,0         ;edx:eax = eax + ebx

This is essentially the same as zero-extending the values to 64-bit before the addition, then doing a 64-bit addition.

For signed integers this won't work (eg "0 + (-1) != 0x00000000 + 0xFFFFFFFF != 0x00000000FFFFFFFF") because you need to sign-extend instead of zero-extending. To do that:

    cdq                 ;Set all bits in edx to the sign of eax
    xchg ebx,eax        ;eax = original ebx
    mov ecx,edx         ;ecx:ebx = original eax sign extended
    cdq                 ;edx:eax = original ebx sign extended

    add eax,ebx
    adc edx,ecx         ;edx:eax = eax + ebx

A "possibly slower depending on which CPU it is" (see note) alternative is to force them into the range of unsigned integers by adding 0x80000000 to them, then correct the result by subtracting 2*0x80000000 (or 0x0000000100000000) from it. Subtracting 0x0000000100000000 is the same as subtracting 1 from the high dword, which is the same as adding 0xFFFFFFFF to the high dword, so it can be:

    add eax,0x80000000
    add ebx,0x80000000

    xor edx,edx         ;Set edx to zero
    add eax,ebx
    adc edx,0xFFFFFFFF  ;edx:eax = eax + 0x80000000 + ebx + 0x80000000 + (-0x0000000100000000)

Note: If you care about performance; this alternative gives you the opportunity to add the 0x80000000 to the values in earlier code (wherever the values come from), and can often end up faster (especially if the same 32-bit values are used multiple times, and/or if the addition can be incorporated into other calculations for free).

For "mixed types" you'd only need to promote the signed value to 64-bit. For example, if EAX is signed and EBX is unsigned:

    cdq                 ;Set all bits in edx to the sign of eax
    add eax,ebx
    adc edx,0           ;edx:eax = eax + ebx

Of course for newer CPUs you'd use 64-bit code. For unsigned 32-bit values they'll already be zero extended by default, and you'd only need a single add rax,rbx instruction. For signed numbers you may need to sign extend (if you can't/didn't sign extend them beforehand), like:

    movsx rax,eax
    movsx rbx,ebx
    add rax,rbx

If I understand the question correctly, you have two 32bit integers that you add to possibly give a 64bit integer. You want to do this without 32bit overflow.

How about looking at what a compiler does:

$ cat add64.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main (int argc, char **argv) {
    uint32_t a, b;

    a = strtoll(argv[1], NULL, 10);
    b = strtoll(argv[2], NULL, 10);
    printf("%lu + %lu = %llu\n", a, b, (uint64_t) a + b);
    return 0;
}
$ gcc -m32 -g -o add64 add64.c 
$ ./add64 3000000000 4000000000
3000000000 + 4000000000 = 7000000000
$ gcc -m32 -g -fverbose-asm -masm=intel -S add64.c
$ $EDITOR add64.s &
[5] 340
$

The relevant generated assembly is:

    mov %ecx, DWORD PTR [%ebp-16]   # D.2300, a
    mov %ebx, 0 # D.2300,
    mov %eax, DWORD PTR [%ebp-12]   # D.2301, b
    mov %edx, 0 # D.2301,
    add %eax, %ecx  # D.2302, D.2300
    adc %edx, %ebx  # D.2302, D.2300

In order to add 64 bit numbers on a 32 bit machine, you have to first move the top half of the 64bit number into register eax, then the second half into edx. When manipulating this number, you'll have to keep track how the number was placed in eax/edx.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM