# Assembly x8086 (emu8086) - Display 32bits number on screen

this is my code (assembly x8086, not MIPS, and I'm using emu8086) to display a 32-bits number on screen. Of course the basic algorithm is as follows:

Input: Number
Set r=0,q=Number,counter=0;
while q > 0 do
divide q by 10
q <- Quotidient, r <- Remainder;
push r;
counter = counter + 1;
end while

while counter > 0 do
pop r;
counter = counter - 1;
display
end while


However the problem is that on x8086 processors, all the registers are 16-bits. So there is no straightforward way to use the div command for division with 32-bit numbers (actually there are some solutions but I find them to be complicated). So I decided to deal with the high and low parts of the 32-bit number separately:

Let A be the number in question, we now divide by 10:
A = q*10 + r (0 <= r <= 9)
now separate A into the high and low parts:
A_high * 2^16 + A_low = q*10 + r (0 <= r <= 9)
our task is to find q and r. To do that we first divide the high part:
A_high = q_high * 10 + r_high (0<= r_high <= 9)
=> A_high * 2^16 = (q_high*2^16)*10 + r_high * 2^16 . Note that r_high is from 0 to 9, so to divide r_high * 2^16 by 10, we simply need to perform the calculations and then store the results in a lookup table! The result:
r_high * 2^16 = q_high_redundant * 10 + r_high_redundant (0 <= r_high_redundant <= 9) (found by using a lookup table) (an interesting note: q_high_redundant is only 16 bits!)
Now divide the low part:
A_low = q_low * 10 + r_low
=> A = A_high * 2^16 + A_low = (q_high*2^16 + q_low + q_high_redundant)*10 + r_low + r_high_redundant
Now you just have to divide r_low + r_high_redundant and add in to the quotient, then you get the results.


Here is my code, please give me some feedback on aesthetics, code optimization,... thank you very much:

;Written by Dang Manh Truong
.stack      100h
.data
base_10     dw      10
var_32bits_high     dw      0
var_32bits_low     dw      0
quotidient_32bits_high      dw      0
quotidient_32bits_low       dw      0
lowest_signed_32bits_high        dw     8000h
lowest_signed_32bits_low         dw     0000h
lowest_signed_32bits_string      dw     "-2147483648" qhigh dw 0 rhigh dw 0 qlow dw 0 rlow dw 0 qhigh_redundant dw 0 rhigh_redundant dw 0 q_0 dw 0 qhigh0 equ 0h rhigh0 equ 0h qhigh1 equ 1999h rhigh1 equ 6h qhigh2 equ 3333h rhigh2 equ 2h qhigh3 equ 4CCCh rhigh3 equ 8h qhigh4 equ 6666h rhigh4 equ 4h qhigh5 equ 8000h rhigh5 equ 0h qhigh6 equ 9999h rhigh6 equ 6h qhigh7 equ 0B333h rhigh7 equ 2h qhigh8 equ 0CCCCh rhigh8 equ 8h qhigh9 equ 0E666h rhigh9 equ 4h .code main proc ;Initialization mov ax,@data mov ds,ax ;example: 7654321 = 0074CBB1h ; mov ax,74h ; mov var_32bits_high,ax ; mov ax,0CBB1h ; mov var_32bits_low,ax ;example: 10223803 = 009C0BBh ; mov ax,9Ch ; mov var_32bits_high,ax ; mov ax,0BBh ; mov var_32bits_low,ax ;example: 32763 ; mov ax,0h ; mov var_32bits_high,ax ; mov ax,32763 ; mov var_32bits_low,ax ;example: 86420 = 00015194h ; mov ax,1h ; mov var_32bits_high,ax ; mov ax,5194h ; mov var_32bits_low,ax ;example: 2147483647 (2^31 - 1) = 7FFFFFFFh ; mov ax,7FFFh ; mov var_32bits_high,ax ; mov ax,0FFFFh ; mov var_32bits_low,ax ;example: -2147483648 (-2^31)= 80000000h ; mov ax,8000h ; mov var_32bits_high,ax ; mov ax,0000h ; mov var_32bits_low,ax ;example: -1 = FFFF FFFFh mov ax,0FFFFh mov var_32bits_high,ax mov ax,0FFFFh mov var_32bits_low,ax mov ax,0 mov bx,0 ;bx: quotidient_32bits_high mov dx,0 ;dx: quotidient_32bits_low mov cx,0 ;counter = 0 ;16bits or 32bits ? mov ax,var_32bits_high cmp ax,0 jne _32bits_routine jmp _16bits_routine ;;; _32bits_routine: mov cx,0 ;if == -2147483648 (-2^31) mov ax,var_32bits_high cmp ax,lowest_signed_32bits_high jne check_if_neg mov ax,var_32bits_low cmp ax,lowest_signed_32bits_low jne check_if_neg ;then lea dx,lowest_signed_32bits_string mov ah,9 int 21h jmp return_to_dos ;if < 0 check_if_neg: mov ax,var_32bits_high cmp ax,0 jnl preparations ;then print "-" ... mov ah,2 mov dl,'-' int 21h ;... and negate number mov ax,var_32bits_high xor ax,negate_mask mov var_32bits_high,ax mov ax,var_32bits_low xor ax,negate_mask inc ax mov var_32bits_low,ax jnc preparations mov ax,var_32bits_high inc ax mov var_32bits_high,ax preparations: mov ax,var_32bits_high mov quotidient_32bits_high,ax mov ax,var_32bits_low mov quotidient_32bits_low,ax while_32bits: ; while >0 do mov ax,quotidient_32bits_high cmp ax,0 jne div_high_part mov ax,quotidient_32bits_low cmp ax,0 jne div_high_part jmp print_char div_high_part: ;divide high part mov dx,0 mov ax,quotidient_32bits_high div base_10 mov qhigh,ax mov rhigh,dx ;case rhigh mov ax,rhigh cmp ax,0 je _rhigh0 cmp ax,1 je _rhigh1 cmp ax,2 je _rhigh2 cmp ax,3 je _rhigh3 cmp ax,4 je _rhigh4 cmp ax,5 je _rhigh5 cmp ax,6 je _rhigh6 cmp ax,7 je _rhigh7 cmp ax,8 je _rhigh8 cmp ax,9 je _rhigh9 _rhigh0: mov ax,qhigh0 mov qhigh_redundant,ax mov ax,rhigh0 mov rhigh_redundant,ax jmp _aftercase _rhigh1: mov ax,qhigh1 mov qhigh_redundant,ax mov ax,rhigh1 mov rhigh_redundant,ax jmp _aftercase _rhigh2: mov ax,qhigh2 mov qhigh_redundant,ax mov ax,rhigh2 mov rhigh_redundant,ax jmp _aftercase _rhigh3: mov ax,qhigh3 mov qhigh_redundant,ax mov ax,rhigh3 mov rhigh_redundant,ax jmp _aftercase _rhigh4: mov ax,qhigh4 mov qhigh_redundant,ax mov ax,rhigh4 mov rhigh_redundant,ax jmp _aftercase _rhigh5: mov ax,qhigh5 mov qhigh_redundant,ax mov ax,rhigh5 mov rhigh_redundant,ax jmp _aftercase _rhigh6: mov ax,qhigh6 mov qhigh_redundant,ax mov ax,rhigh6 mov rhigh_redundant,ax jmp _aftercase _rhigh7: mov ax,qhigh7 mov qhigh_redundant,ax mov ax,rhigh7 mov rhigh_redundant,ax jmp _aftercase _rhigh8: mov ax,qhigh8 mov qhigh_redundant,ax mov ax,rhigh8 mov rhigh_redundant,ax jmp _aftercase _rhigh9: mov ax,qhigh9 mov qhigh_redundant,ax mov ax,rhigh9 mov rhigh_redundant,ax jmp _aftercase _aftercase: ;divide low part mov ax,0 mov q_0,ax mov dx,0 mov ax,quotidient_32bits_low div base_10 mov qlow,ax mov rlow,dx mov ax,rlow add ax,rhigh_redundant ;if remainder >= 10 cmp ax,base_10 jl after_if sub ax,base_10 mov dx,1 mov q_0,dx after_if: mov rlow,ax mov ax,q_0 add ax,qlow mov qlow,ax jnc label1 mov ax,qhigh inc ax mov qhigh,ax label1: mov ax,qlow add ax,qhigh_redundant mov qlow,ax jnc label2 mov ax,qhigh inc ax mov qhigh,ax label2: ;push remainder to stack mov ax,rlow push ax inc cx mov ax,qhigh mov quotidient_32bits_high,ax mov ax,qlow mov quotidient_32bits_low,ax jmp while_32bits ;;; _16bits_routine: mov ax,var_32bits_low mov bx,0 ;bx: quotient mov cx,0 while_loop: cmp ax,0 je print_char mov dx,0 div base_10 mov bx,ax ;ax stores quotidient mov ax,dx ;dx stores remainder ;push remainder push ax ;counter = counter + 1 inc cx ;numerator = quotidient mov ax,bx jmp while_loop print_char: cmp cx,0 je return_to_dos pop ax ;because at this point 0 <= ax <= 9, setting ah = 2 does not change the results mov ah,2 mov dl,al add dl,30h ;0-> '0',1->'1',.... int 21h dec cx jmp print_char return_to_dos: mov ah,4ch int 21h main endp end main  ## 3 Answers Let's first concentrate on the 16 bit version. • Choosing a While-loop is not the best choice. You know that you'll need at the very least 1 character to print (even if the input was 0), and so a Repeat-Until-loop is better. This also avoids having to check for CX being zero before starting outputting with DOS. • Why should you first move the remainder to another register, when all you want to do is just push it on the stack? • There's also no point in moving the quotient back and forth in another register. • When converting the remainder into a character (through adding 30h) it's shorter to do it while the remainder is still in the AL register. I'll don't use it in the below code because popping directly in the DX register is a bit shorter still. • Clearing a register is best done by xoring it with itself. • Checking to see if a register is empty can be done by comparing it with zero, but a shorter way is to test the register with itself and then decide upon the state of the zero flag (ZF). All put together:  mov ax, var_32bits_low xor cx, cx repeat_loop: xor dx, dx div base_10 push dx ;push remainder inc cx ;counter = counter + 1 test ax, ax jnz repeat_loop print_char: pop dx add dl, 30h ;0 -> '0', 1 ->'1',... mov ah, 02h ;DOS.PrintChar int 21h loop print_char  Do note that writing tail comments instead of full line comments gives cleaner and more readable code. Also alignment is everything in Assembly programming. Very, very important! var_32bits_high dw 0 var_32bits_low dw 0 quotidient_32bits_high dw 0 quotidient_32bits_low dw 0 negate_mask equ 0FFFFh lowest_signed_32bits_high dw 8000h lowest_signed_32bits_low dw 0000h lowest_signed_32bits_string dw "-2147483648"


These are a few remarks from just looking at this small section of the program:

• Alignment!
• It's clearer to put the equates apart from the data definitions.
• Although the high and low variables are used separately, it's still a good idea to follow the little endianess convention and have the low word stored before the high word.
• Your lowest_signed_32bits_string needs to be defined using db instead of dw. This is an error.

    mov     ax,0
mov     bx,0            ;bx: quotidient_32bits_high
mov     dx,0            ;dx: quotidient_32bits_low
mov     cx,0            ;counter = 0
;16bits or 32bits ?
mov     ax,var_32bits_high
cmp     ax,0
jne     _32bits_routine
jmp     _16bits_routine


As said before, clear registers using xor reg, reg.
Furthermore you can compare memory with an immediate directly, no need to do it through using a register.
Had you written the (short) 16-bit version above the (long) 32-bit version, the conditional jump could reach the 32-bit version easily.

    xor  ax, ax
xor  bx, bx             ;bx: quotidient_32bits_high
xor  dx, dx             ;dx: quotidient_32bits_low
xor  cx, cx             ;counter = 0
cmp  var_32bits_high, 0 ;16bits or 32bits ?
jne  _32bits_routine
_16bits_routine:
...
_32bits_routine:
...


You'll agree that much that was said about the 16 bit version also applies to the 32 bit version. To be honest, I found it lacks so much on comments that I hesitate to actually review it thoroughly. I will however point out the next optimizations:

;... and negate number
mov     ax,var_32bits_high
mov     var_32bits_high,ax


Since the negate_mask is just 0FFFFh, this code can be written as a mere not.

not  var_32bits_high


And here you only want to increment the qhigh variable when there's a carry from the previous operation

    jnc     label1
mov     ax,qhigh
inc     ax
mov     qhigh,ax
label1:


Write this very much simpler using the AddWithCarry adc instruction:

    adc  qhigh, 0


This is a direct translation of Roland's work (java -> asm). For clarity, I have made no effort to optimize (despite enormous temptation). The comments are the associated java lines.

Note that despite my concerns, this does work correctly:

; using 123456789 aka 75B CD15
mov dx, 075bh
mov ax, 0cd15h

; input:
;   dx:ax = number to be divided by 10
; output:
;   dx:ax = old dx:ax div 10
;   bx = remainder
;   cx, si, di, flags undefined

; cx = 10;
mov cx, 10

; si = dx;
mov si, dx

; di = (short) ((ax >> 8) & 0xff);
mov di, ax
shr di, 8
and di, 0ffh

; bx = (short) (ax & 0xff);
mov bx, ax
and bx, 0ffh

; dx = 0;
mov dx, 0

; ax = si;
mov ax, si

; udiv_cx();
div cx

; si = ax;
mov si, ax

; ax = (short) (((dx & 0xff) << 8) | di);
mov ax, dx
and ax, 0ffh
shl ax, 8
or ax, di

; dx = 0;
mov dx, 0

; udiv_cx();
div cx

; di = ax;
mov di, ax

; ax = (short) (((dx & 0xff) << 8) | bx);
mov ax, dx
and ax, 0ffh
shl ax, 8
or ax, bx

; dx = 0;
mov dx, 0

; udiv_cx();
div cx

; cx = ax;
mov cx, ax

; bx = dx;
mov bx, dx

; dx = si;
mov dx, si

; ax = di;
mov ax, di

; ax <<= 8;
shl ax, 8

; ax |= cx;
or ax, cx


On output, dx:ax is BC 614E (aka 12345678) as promised. I have not tested this extensively, but it seems plausible. Since I had the translation, I thought I'd share.

To me, the code looks too long. Therefore I implemented a different algorithm. I wrote it in Java, but it should be trivial to translate into 8086 assembly.

public class Div32 {

short ax, bx, cx, dx, si, di;

// input:
//   dx:ax = number to be divided by 10
// output:
//   dx:ax = old dx:ax div 10
//   bx = remainder
//   cx, si, di, flags undefined
void udiv32_10() {
cx = 10;

si = dx;
di = (short) ((ax >> 8) & 0xff);
bx = (short) (ax & 0xff);

dx = 0;
ax = si;
udiv_cx();
si = ax;

ax = (short) (((dx & 0xff) << 8) | di);
dx = 0;
udiv_cx();
di = ax;

ax = (short) (((dx & 0xff) << 8) | bx);
dx = 0;
udiv_cx();
cx = ax;

bx = dx;
dx = si;
ax = di;
ax <<= 8;
ax |= cx;
}

// emulates the 8086 "udiv cx" instruction, ignoring overflow,
// since that cannot happen in this program
void udiv_cx() {
int x = (dx & 0xffff) << 16 | (ax & 0xffff);
ax = (short) (x / cx);
dx = (short) (x % cx);
}

void print32() {
byte[] out = new byte[20]; // to be allocated on the call stack
int outptr = 20;
out[--outptr] = '\$'; // string terminator for int 21h, function 09h
do {
udiv32_10();
out[--outptr] = (byte) ((bx & 0xff) + '0');
} while (ax != 0);
System.out.println(new String(out, outptr, 20 - outptr));
}

void testPrint() {
int x = 123456789;
dx = (short) (x >> 16);
ax = (short) (x & 0xffff);
print32();
}

public static void main(String[] args) {
new Div32().testPrint();
}
}


The basic idea is to provide a function that does the 32-bit division. After that, the rest becomes simple.

To compute aa:bb:cc:dd / 10, the code goes:

t1    = aa:bb % 10
aa:bb = aa:bb / 10
t2    = t1:cc % 10
cc    = t1:cc / 10
rem   = t2:dd % 10
dd    = t2:dd / 10

• Of course it would be shorter in Java than it would in Assembly! It's so obvious :( Mar 20, 2017 at 2:03
• Did you have a look at it? In assembly it's equally short. The variable names should ring a bell immediately. Mar 20, 2017 at 6:59
• Does this actually work though? Your udiv_cx seems to assume that it can create a 32bit int. If you could do that, the problem is trivial. But I thought the point here was that the 8086 didn't support 32bit anythings. Mar 21, 2017 at 8:49
• The udiv_cx emulates exactly what the udiv cx instruction does. I thought it would be obvious from the name, but it seems I guessed wrong. Mar 21, 2017 at 23:44
• Do you have any feedback on the OP’s code, other than that it looks “too long?” Answers that only give an alternative solution, without explanation, are discouraged here. Feb 3, 2023 at 23:34