assemblyx86nasmbigintextended-precision

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, i.e. in a 64 bit "pseudo register" eax:edx. Using Intel syntax assembler.


Solution

  • 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 (e.g. "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