4
\$\begingroup\$

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
negate_mask         equ      0FFFFh  
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
\$\endgroup\$

3 Answers 3

5
\$\begingroup\$

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 
    xor     ax,negate_mask
    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
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
0
\$\begingroup\$

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
\$\endgroup\$
5
  • \$\begingroup\$ Of course it would be shorter in Java than it would in Assembly! It's so obvious :( \$\endgroup\$ Mar 20, 2017 at 2:03
  • \$\begingroup\$ Did you have a look at it? In assembly it's equally short. The variable names should ring a bell immediately. \$\endgroup\$ Mar 20, 2017 at 6:59
  • \$\begingroup\$ 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. \$\endgroup\$ Mar 21, 2017 at 8:49
  • \$\begingroup\$ 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. \$\endgroup\$ Mar 21, 2017 at 23:44
  • \$\begingroup\$ 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. \$\endgroup\$
    – Davislor
    Feb 3, 2023 at 23:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.