Search code examples
assemblyx86integer-division

x86 Assembly - idiv to get decimal places


I'm limited to using the .386 with my current process. Using intel x86 how do I get the decimal places of a division? Basically I want it to output to the hundredth place like: 12.81

Here is my current code:

mov AX, Dividend
cwd
idiv Divisor

outputW AX ;Outputs Quotient (whole number)
outputW DX ;Outputs Remainder

I've tried multiplying AX by 100 first, but that still did not work for me. I'm also currently limited to making the variables Dividend and Divisor Word size. I tried replacing cwd with the cwde command, but that didn't work for me.

Any help would be greatly appreciated!

Thanks


Solution

  • You can multiply the dividend by 100 before performing the division, adjust for rounding, then divide by 100 again to get the two decimal places. Since your initial code is 16-bit, here is a 16-bit solution first:

        ;; Perform the initial division (100 * Dividend / Divisor)
        MOV AX, Dividend    ; Load the Dividend into AX
        IMUL OneHundred     ; DX:AX = AX * 100
        IDIV Divisor        ; DX:AX / Divisor... AX=Quotient, DX=Remainder
    
        ;; Handle the remainder
        SHL DX, 1           ; DX now equals the remainder times two
        CMP DX, Divisor     ; Compare Remainder*2 to the Divisor
        JL l1               ; If Remainder*2 is <, we round down (nothing changes)
        INC AX              ; If Remainder*2 is >=, we round up (increment the quotient)
    l1:
    
        ;; Divide by 100 again to get the final result
        CWD                 ; Sign-extend AX to DX:AX
        IDIV OneHundred     ; DX:AX / 100... AX=Quotient, DX=Remainder
        MOV IntegerPart, AX ; Now AX is the integer part
        MOV DecimalPart DX  ; And DX is the fractional part
    
    OneHundred DW 100
    

    Or, if a 32-bit solution is applicable, you can use 32-bit integers instead:

        ;; Perform the initial division (100 * Dividend / Divisor)
        MOVSX EAX, Dividend ; Sign-extend the Dividend into EAX
        IMUL OneHundred     ; EDX:EAX = EAX * 100
        MOVSX ECX, Divisor  ; Sign-extend the Divisor into ECX
        IDIV ECX            ; EDX:EAX / Divisor... EAX=Quotient, EDX=Remainder
    
        ;; Handle the remainder
        SHL EDX, 1          ; EDX now equals the remainder times two
        CMP EDX, ECX        ; Compare Remainder*2 to the Divisor
        JL l1               ; If Remainder*2 is <, we round down (nothing changes)
        INC EAX             ; If Remainder*2 is >=, we round up (increment the quotient)
    l1:
    
        ;; Divide by 100 again to get the final result
        CDQ                 ; Sign-extend EAX to EDX:EAX
        IDIV OneHundred     ; EDX:EAX / 100... EAX=Quotient, EDX=Remainder
        MOV IntegerPart, EAX ; Now EAX is the integer part
        MOV DecimalPart EDX  ; And EDX is the fractional part
    
    OneHundred DD 100
    

    Note: The 32-bit code is preferable since it does not produce Divide Errors from large quotients. Consider Dividend=32767 and Divisor=1 in the 16-bit case:

    • The first multiplication does: DX:AX = 32767*100 = 3276700
    • The first division does: DX:AX / 1 = 3276700 / 1 = 3276700 (with a zero remainder), however 3276700 is too large to fit in AX (it is greater than 32767) so a Divide Error is produced.

    This does not happen on 32-bit because 3276700 can fit in EAX. (Of course, dividing by zero would still produce an error in either case)

    Another note: This code assumes the result of the initial division is positive