displayNumber.txt 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. Method 2 : division by const 10
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. mov bx,10 ;CONST
  10. xor cx,cx ;Reset counter
  11. .a: xor dx,dx ;Setup for division DX:AX / BX
  12. div bx ; -> AX is Quotient, Remainder DX=[0,9]
  13. push dx ;(1) Save remainder for now
  14. inc cx ;One more digit
  15. test ax,ax ;Is quotient zero?
  16. jnz .a ;No, use as next dividend
  17. .b: pop dx ;(1)
  18. add dl,"0" ;Turn into character [0,9] -> ["0","9"]
  19. mov ah,02h ;DOS.DisplayCharacter
  20. int 21h ; -> AL
  21. loop .b
  22. This second method has none of the drawbacks of the first method:
  23. Because we stop when a quotient becomes zero, there's never any problem with ugly leading zeroes.
  24. The divider is fixed. That's easy enough.
  25. It's real simple to apply this method to displaying larger numbers and that's precisely what comes next.
  26. Displaying the unsigned 32-bit number held in DX:AX
  27. On 8086 a cascade of 2 divisions is needed to divide the 32-bit value in DX:AX by 10.
  28. 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.
  29. To check if the dword in DX:AX is zero, I've OR-ed both halves in a scratch register.
  30. 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.
  31. Other than that this snippet is similar to method 2 above.
  32. mov bx,10 ;CONST
  33. push bx ;Sentinel
  34. .a: mov cx,ax ;Temporarily store LowDividend in CX
  35. mov ax,dx ;First divide the HighDividend
  36. xor dx,dx ;Setup for division DX:AX / BX
  37. div bx ; -> AX is HighQuotient, Remainder is re-used
  38. xchg ax,cx ;Temporarily move it to CX restoring LowDividend
  39. div bx ; -> AX is LowQuotient, Remainder DX=[0,9]
  40. push dx ;(1) Save remainder for now
  41. mov dx,cx ;Build true 32-bit quotient in DX:AX
  42. or cx,ax ;Is the true 32-bit quotient zero?
  43. jnz .a ;No, use as next dividend
  44. pop dx ;(1a) First pop (Is digit for sure)
  45. .b: add dl,"0" ;Turn into character [0,9] -> ["0","9"]
  46. mov ah,02h ;DOS.DisplayCharacter
  47. int 21h ; -> AL
  48. pop dx ;(1b) All remaining pops
  49. cmp dx,bx ;Was it the sentinel?
  50. jb .b ;Not yet
  51. Displaying the signed 32-bit number held in DX:AX
  52. The procedure is as follows:
  53. First find out if the signed number is negative by testing the sign bit.
  54. If it is, then negate the number and output a "-" character but beware to not destroy the number in DX:AX in the process.
  55. The rest of the snippet is the same as for an unsigned number.
  56. test dx,dx ;Sign bit is bit 15 of high word
  57. jns .a ;It's a positive number
  58. neg dx ;\
  59. neg ax ; | Negate DX:AX
  60. sbb dx,0 ;/
  61. push ax dx ;(1)
  62. mov dl,"-"
  63. mov ah,02h ;DOS.DisplayCharacter
  64. int 21h ; -> AL
  65. pop dx ax ;(1)
  66. .a: mov bx,10 ;CONST
  67. push bx ;Sentinel
  68. .b: mov cx,ax ;Temporarily store LowDividend in CX
  69. mov ax,dx ;First divide the HighDividend
  70. xor dx,dx ;Setup for division DX:AX / BX
  71. div bx ; -> AX is HighQuotient, Remainder is re-used
  72. xchg ax,cx ;Temporarily move it to CX restoring LowDividend
  73. div bx ; -> AX is LowQuotient, Remainder DX=[0,9]
  74. push dx ;(2) Save remainder for now
  75. mov dx,cx ;Build true 32-bit quotient in DX:AX
  76. or cx,ax ;Is the true 32-bit quotient zero?
  77. jnz .b ;No, use as next dividend
  78. pop dx ;(2a) First pop (Is digit for sure)
  79. .c: add dl,"0" ;Turn into character [0,9] -> ["0","9"]
  80. mov ah,02h ;DOS.DisplayCharacter
  81. int 21h ; -> AL
  82. pop dx ;(2b) All remaining pops
  83. cmp dx,bx ;Was it the sentinel?
  84. jb .c ;Not yet
  85. Will I need separate routines for different number sizes?
  86. 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:
  87. ; IN (al) OUT ()
  88. DisplaySignedNumber8:
  89. push ax
  90. cbw ;Promote AL to AX
  91. call DisplaySignedNumber16
  92. pop ax
  93. ret
  94. ; -------------------------
  95. ; IN (ax) OUT ()
  96. DisplaySignedNumber16:
  97. push dx
  98. cwd ;Promote AX to DX:AX
  99. call DisplaySignedNumber32
  100. pop dx
  101. ret
  102. ; -------------------------
  103. ; IN (dx:ax) OUT ()
  104. DisplaySignedNumber32:
  105. push ax bx cx dx
  106. ...
  107. Alternatively, if you don't mind the clobbering of the AX and DX registers use this fall-through solution:
  108. ; IN (al) OUT () MOD (ax,dx)
  109. DisplaySignedNumber8:
  110. cbw
  111. ; --- --- --- --- -
  112. ; IN (ax) OUT () MOD (ax,dx)
  113. DisplaySignedNumber16:
  114. cwd
  115. ; --- --- --- --- -
  116. ; IN (dx:ax) OUT () MOD (ax,dx)
  117. DisplaySignedNumber32:
  118. push bx cx
  119. ...
  120. 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. –
  121. Peter Cordes
  122. Commented Oct 22, 2017 at 22:08