| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- Method 2 : division by const 10
- Processing the number going from the right to the left seems counter-intuitive since our goal is to display the leftmost digit first. But as you're about to find out, it works beautifully.
- By dividing the number (65535) by 10, we obtain a quotient (6553) that will become the dividend in the next step. We also get a remainder (5) that we can't output just yet and so we'll have to save in somewhere. The stack is a convenient place to do so.
- By dividing the quotient from the previous step (6553) by 10, we obtain a quotient (655) that will become the dividend in the next step. We also get a remainder (3) that we can't just yet output and so we'll have to save it somewhere. The stack is a convenient place to do so.
- By dividing the quotient from the previous step (655) by 10, we obtain a quotient (65) that will become the dividend in the next step. We also get a remainder (5) that we can't just yet output and so we'll have to save it somewhere. The stack is a convenient place to do so.
- By dividing the quotient from the previous step (65) by 10, we obtain a quotient (6) that will become the dividend in the next step. We also get a remainder (5) that we can't just yet output and so we'll have to save it somewhere. The stack is a convenient place to do so.
- By dividing the quotient from the previous step (6) by 10, we obtain a quotient (0) that signals that this was the last division. We also get a remainder (6) that we could output as a character straight away, but refraining from doing so turns out to be most effective and so as before we'll save it on the stack.
- At this point the stack holds our 5 remainders, each being a single digit number in the range [0,9]. Since the stack is LIFO (Last In First Out), the value that we'll POP first is the first digit we want displayed. We use a separate loop with 5 POP's to display the complete number. But in practice, since we want this routine to be able to also deal with numbers that have fewer than 5 digits, we'll count the digits as they arrive and later do that many POP's.
- mov bx,10 ;CONST
- xor cx,cx ;Reset counter
- .a: xor dx,dx ;Setup for division DX:AX / BX
- div bx ; -> AX is Quotient, Remainder DX=[0,9]
- push dx ;(1) Save remainder for now
- inc cx ;One more digit
- test ax,ax ;Is quotient zero?
- jnz .a ;No, use as next dividend
- .b: pop dx ;(1)
- add dl,"0" ;Turn into character [0,9] -> ["0","9"]
- mov ah,02h ;DOS.DisplayCharacter
- int 21h ; -> AL
- loop .b
- This second method has none of the drawbacks of the first method:
- Because we stop when a quotient becomes zero, there's never any problem with ugly leading zeroes.
- The divider is fixed. That's easy enough.
- It's real simple to apply this method to displaying larger numbers and that's precisely what comes next.
- Displaying the unsigned 32-bit number held in DX:AX
- On 8086 a cascade of 2 divisions is needed to divide the 32-bit value in DX:AX by 10.
- The 1st division divides the high dividend (extended with 0) yielding a high quotient. The 2nd division divides the low dividend (extended with the remainder from the 1st division) yielding the low quotient. It's the remainder from the 2nd division that we save on the stack.
- To check if the dword in DX:AX is zero, I've OR-ed both halves in a scratch register.
- Instead of counting the digits, requiring a register, I chose to put a sentinel on the stack. Because this sentinel gets a value (10) that no digit can ever have ([0,9]), it nicely allows to determine when the display loop has to stop.
- Other than that this snippet is similar to method 2 above.
- mov bx,10 ;CONST
- push bx ;Sentinel
- .a: mov cx,ax ;Temporarily store LowDividend in CX
- mov ax,dx ;First divide the HighDividend
- xor dx,dx ;Setup for division DX:AX / BX
- div bx ; -> AX is HighQuotient, Remainder is re-used
- xchg ax,cx ;Temporarily move it to CX restoring LowDividend
- div bx ; -> AX is LowQuotient, Remainder DX=[0,9]
- push dx ;(1) Save remainder for now
- mov dx,cx ;Build true 32-bit quotient in DX:AX
- or cx,ax ;Is the true 32-bit quotient zero?
- jnz .a ;No, use as next dividend
- pop dx ;(1a) First pop (Is digit for sure)
- .b: add dl,"0" ;Turn into character [0,9] -> ["0","9"]
- mov ah,02h ;DOS.DisplayCharacter
- int 21h ; -> AL
- pop dx ;(1b) All remaining pops
- cmp dx,bx ;Was it the sentinel?
- jb .b ;Not yet
- Displaying the signed 32-bit number held in DX:AX
- The procedure is as follows:
- First find out if the signed number is negative by testing the sign bit.
- If it is, then negate the number and output a "-" character but beware to not destroy the number in DX:AX in the process.
- The rest of the snippet is the same as for an unsigned number.
- test dx,dx ;Sign bit is bit 15 of high word
- jns .a ;It's a positive number
- neg dx ;\
- neg ax ; | Negate DX:AX
- sbb dx,0 ;/
- push ax dx ;(1)
- mov dl,"-"
- mov ah,02h ;DOS.DisplayCharacter
- int 21h ; -> AL
- pop dx ax ;(1)
- .a: mov bx,10 ;CONST
- push bx ;Sentinel
- .b: mov cx,ax ;Temporarily store LowDividend in CX
- mov ax,dx ;First divide the HighDividend
- xor dx,dx ;Setup for division DX:AX / BX
- div bx ; -> AX is HighQuotient, Remainder is re-used
- xchg ax,cx ;Temporarily move it to CX restoring LowDividend
- div bx ; -> AX is LowQuotient, Remainder DX=[0,9]
- push dx ;(2) Save remainder for now
- mov dx,cx ;Build true 32-bit quotient in DX:AX
- or cx,ax ;Is the true 32-bit quotient zero?
- jnz .b ;No, use as next dividend
- pop dx ;(2a) First pop (Is digit for sure)
- .c: add dl,"0" ;Turn into character [0,9] -> ["0","9"]
- mov ah,02h ;DOS.DisplayCharacter
- int 21h ; -> AL
- pop dx ;(2b) All remaining pops
- cmp dx,bx ;Was it the sentinel?
- jb .c ;Not yet
- Will I need separate routines for different number sizes?
- In a program where you need to display on occasion AL, AX, or DX:AX, you could just include the 32-bit version and use next little wrappers for the smaller sizes:
- ; IN (al) OUT ()
- DisplaySignedNumber8:
- push ax
- cbw ;Promote AL to AX
- call DisplaySignedNumber16
- pop ax
- ret
- ; -------------------------
- ; IN (ax) OUT ()
- DisplaySignedNumber16:
- push dx
- cwd ;Promote AX to DX:AX
- call DisplaySignedNumber32
- pop dx
- ret
- ; -------------------------
- ; IN (dx:ax) OUT ()
- DisplaySignedNumber32:
- push ax bx cx dx
- ...
- Alternatively, if you don't mind the clobbering of the AX and DX registers use this fall-through solution:
- ; IN (al) OUT () MOD (ax,dx)
- DisplaySignedNumber8:
- cbw
- ; --- --- --- --- -
- ; IN (ax) OUT () MOD (ax,dx)
- DisplaySignedNumber16:
- cwd
- ; --- --- --- --- -
- ; IN (dx:ax) OUT () MOD (ax,dx)
- DisplaySignedNumber32:
- push bx cx
- ...
- You could tighten up the decreasing-powers-of-10 version by delaying the xchg (and only using mov for speed instead of code-size). div / push dx / add al,'0' (short encoding) / mov dl, al / mov ah, 2. Or you could take advantage of the fact that the quotient leaves ah zero to add ax, '0' + (2<<8) / mov dx, ax to leave ah=2 and dl=ASCII_quotient, but that comes at the expense of readability so it's not good for beginners. –
- Peter Cordes
- Commented Oct 22, 2017 at 22:08
|