patternMinor
Assembly x8086 (emu8086) - Display 32bits number on screen
Viewed 0 times
numberx808632bitsemu8086assemblyscreendisplay
Problem
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:
However the problem is that on x8086 processors, all the registers are 16-bits. So there is no straightforward way to use the
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 e
Input: Number
Set r=0,q=Number,counter=0;
while q > 0 do
divide q by 10
q 0 do
pop r;
counter = counter - 1;
display
end whileHowever 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 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 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 e
Solution
Let's first concentrate on the 16 bit version.
All put together:
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!
These are a few remarks from just looking at this small section of the program:
As said before, clear registers using
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.
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:
Since the negate_mask is just 0FFFFh, this code can be written as a mere
And here you only want to increment the qhigh variable when there's a carry from the previous operation
Write this very much simpler using the AddWithCarry
- 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
CXbeing 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
ALregister. I'll don't use it in the below code because popping directly in theDXregister 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
testthe 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
dbinstead ofdw. 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
Context
StackExchange Code Review Q#157926, answer score: 5
Revisions (0)
No revisions yet.