Write 32-bit number on 16-bit CPU

I'm writing some code that needs to be compatible with a 16-bit CPU (8086/8088). I have a 32-bit number stored in two word registers (e.g., DX:AX) and I need to write the value of the number in decimal format. For example, FFFF_FFFFh needs to be written as 4294967295.

Anybody have some sample code on how to do this?


  • After some research, I found a way to do this. The algorithm I discovered is comparatively slow, but is general enough that it can easily be extended to any number of bits (it's not limited to 32). It can also easily be adjusted to work on 32-bit or 64-bit CPU's. If anybody cares, let me know and I'll post the code here.
  • I'd love to see how you did it, if that's OK. I'm working on that exact same problem.

    I'm using an old TMS9900 processor, and need to do some 32 bit arithmetic. I have done the adding and subraction (and by extension, multiplication and division), but the thing that is stumping me, is how do I display a 32 bit value, which is split across two 16 registers, in decimal, when the largest useful dividend I can express (for base 10) is 10,000. Hmmmph....

    So, basically, I have: 1,234,567 which exists in the memory as:

    First 16 bit word: $0012
    Second 16 bit word: $D687

    (The TMS9900 is big endian)

    And I've got to convert that into 1234567 for screen display purposes.

    Crikey. Bit stumped on that one!

    (The TMS9900 has '32 bit' multiply and divide, but....

    Multiply: You can multiply two 16 bit values, giving a 32 bit result.
    Divide: You can divide a 32 bit value, by a 16 bit dividend (hence the largest 'useful' divisor for base 10 is 10,000).

    I'd be extremely interested to see your solution. I hope I can understand Intel assembly!


  • Actually, that's the same way that 8086/8088's work. The CPU can do 32-bit multiplication and division, but uses 16-bit registers, so has the same issues your CPU does. Anyway, here's the code. Hopefully you'll be able to "translate" it so it works on your CPU.

    ;Inputs: DX:AX = DWord to Write
    ;Outputs: To Screen
    PUSH AX,BX,CX,DX ;Save used registers
    PUSH DI,SI,BP ;Save used registers
    MOV SI,03B9Ah ;Start Divisor (SI:DI)
    MOV DI,0CA00h ; at 1,000,000,000
    XOR BP,BP ;Initialize character counter
    D10: ;Loop to here for each possible character
    MOV CX,SI ;CX:BX =
    MOV BX,DI ; Divisor
    CALL Divide32s ;Do the division (rtns Quotient DX:AX, Remainder CX:BX)
    OR AX,AX ;Non-zero character?
    JNZ >D20 ;If so, we need to write it
    OR BP,BP ;Has there already been a non-zero character?
    JNZ >D20 ;If so, we need to write it
    OR SI,SI ;Is this the last character?
    JNZ >D40 ;If not, we don't need to write it
    CMP DI,1 ;Is this the last character?
    JNE >D40 ;If so, we need to write it
    D20: ;Need to write this character
    INC BP ;Increment character counter
    ADD AL,'0' ;Convert the Number to ASCII
    D30: ;Write the character
    CALL WriteAL ;Write it
    D40: ;Done writing character, if appropriate
    OR SI,SI ;Was this the units character?
    JNZ >D45 ;If not, continue
    CMP DI,1 ;Was this the units character?
    JE >D90 ;If so, we're done
    D45: ;Not done yet
    MOV DX,SI ;DX:AX =
    MOV AX,DI ; Current Divisor
    PUSH CX,BX ;Save Current Remainder (New Dividend) on the stack
    XOR CX,CX ;Divisor (CX:BX) =
    MOV BX,10 ; 10
    CALL Divide32s ;Do the division (calculate new Divisor)
    MOV SI,DX ;SI:DI =
    MOV DI,AX ; New Divisor
    POP AX,DX ;DX:AX = New Dividend (Old Remainder)
    JMP D10 ;Keep going
    D90: ;We're done
    POP BP,SI,DI ;Restore used registers
    POP DX,CX,BX,AX ;Restore used registers

    ;Inputs: DX:AX = Dividend
    ; CX:BX = Divisor
    ;Outputs: DX:AX = Quotient
    ; CX:BX = Remainder
    ;NOTES: This is UNSIGNED division!
    ; This is also a slow division process, but the code is small and
    ; we only do it a few times in this program if we do it at all,
    ; so there's no compelling need to optimize for speed.
    ; This does not check for division by 0 (returns -1 instead of an error).
    ; I find the code quite interesting, because it does not actually require
    ; the CPU to do any divison at all -- only shifts, compares, and
    ; subtracts. The main reason it's so slow is that the loop must be
    ; performed all 32 times -- there's no way to exit early.
    ; This implementation came from The Art of Assembly Language by
    ; Randall Hyde. This is an ASM implementation of the following
    ; pseudocode:
    ; Quotient := Remainder;
    ; Remainder := 0;
    ; FOR I := 1 TO NumberOfBits DO
    ; Remainder:Quotient := Remainder:Quotient SHL 1;
    ; IF Remainder >= Divisor THEN
    ; Remainder := Remainder - Divisor;
    ; Quotient := Quotient + 1;
    ; ENDIF
    ; ENDFOR
    ; During the entire routine:
    ; DX:AX = Dividend
    ; CX:BX = Divisor
    ; SI:DI = Remainder
    OR CX,CX ;Is the Divisor really 32 bits?
    JNZ >V20 ;If so, handle it
    CMP DX,BX ;Will we only need to do one division?
    JB >V10 ;If so, jump to handle it
    MOV CX,AX ;CX = Dividend-Low
    MOV AX,DX ;DX:AX =
    XOR DX,DX ; Dividend
    DIV BX ;Do the Division (AX = Quotient)
    XCHG AX,CX ;CX = Quotient-High, AX = Dividend-Low
    V10: ;First division handled, if appropriate
    DIV BX ;AX = Quotient-Low
    MOV BX,DX ;BX = Remainder-Low
    MOV DX,CX ;DX = Quotient-High
    XOR CX,CX ;CX = Remainder-High
    JMP >V90 ;Done

    V20: ;Divisor really is 32 bits
    PUSH DI,SI,BP ;Save used registers
    MOV BP,32 ;Do 32 bits
    XOR SI,SI ;Remainder :=
    XOR DI,DI ; 0
    V30: ;Loop to here for each bit
    SHL AX,1 ;Remainder:Quotient :=
    RCL DX,1 ; Remainder:Quotient
    RCL DI,1 ; SHL
    RCL SI,1 ; 1
    CMP SI,CX ;Remainder HO word more than Divisor HO word?
    JA >V40 ;If so, handle it
    JB >V50 ;If not, we're done with this bit
    V35: ;Remainder HO word = Divisor HO word
    CMP DI,BX ;Remainder LO word more than Divisor LO word?
    JB >V50 ;If not, we're done with this bit
    SUB DI,BX ;Remainder :=
    SBB SI,CX ; Remainder - Divisor
    INC AX ;Increment Quotient
    V50: ;Go to the next bit
    DEC BP ;Decrement Loop Counter
    JNZ V30 ;If not 0 yet, keep looking
    MOV CX,SI ;Put Remainder
    MOV BX,DI ; in CX:BX for the return
    POP BP,SI,DI ;Restore used registers
    V90: ;Done
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!


In this Discussion