确实,DOS 并没有为我们提供直接输出数字的功能。
您必须先自己转换数字,然后让 DOS 显示它
使用文本输出功能之一。
显示 AX 中保存的无符号 16 位数
在处理数字转换问题时,看看如何
组成数字的数字相互关联。
让我们考虑一下数字 65535 及其分解:
(6 * 10000) + (5 * 1000) + (5 * 100) + (3 * 10) + (5 * 1)
方法1:10的递减除法
处理从左到右的数字很方便,因为它
允许我们在提取单个数字后立即显示它。
通过将数字(65535)除以10000,我们得到一位数的商
(6) 我们可以直接输出为一个字符。我们也得到一个余数
(5535)这将成为下一步的红利。
通过将上一步(5535)的余数除以1000,我们得到
我们可以立即将其作为字符输出的个位数商 (5)。
我们还得到了一个余数(535),它将成为下一步的红利。
通过将上一步(535)的余数除以100,我们得到
我们可以立即将其作为字符输出的个位数商 (5)。
我们还得到了一个余数 (35),它将成为下一步的红利。
通过将上一步(35)的余数除以10,我们得到
我们可以立即将其作为字符输出的个位数商 (3)。
我们还会得到一个余数 (5),它将成为下一步的红利。
将上一步(5)的余数除以1,我们得到
我们可以立即将其作为字符输出的个位数商 (5)。
这里余数总是 0。(避免这个 silly 除以 1
需要一些额外的代码)
mov bx,.List
.a: xor dx,dx
div word ptr [bx] ; -> AX=[0,9] is Quotient, Remainder DX
xchg ax,dx
add dl,"0" ;Turn into character [0,9] -> ["0","9"]
push ax ;(1)
mov ah,02h ;DOS.DisplayCharacter
int 21h ; -> AL
pop ax ;(1) AX is next dividend
add bx,2
cmp bx,.List+10
jb .a
...
.List:
dw 10000,1000,100,10,1
虽然这种方法当然会产生正确的结果,但它有一些
缺点:
-
考虑较小的数字 255 及其分解:
(0 * 10000) + (0 * 1000) + (2 * 100) + (5 * 10) + (5 * 1)
如果我们使用相同的 5 步流程,我们会得到“00255”。那2个领先
零是不可取的,我们必须包括额外的指令才能得到
摆脱他们。
分隔线随着每一步而变化。我们必须将分隔符列表存储在
记忆。动态计算这些除法器是可能的,但引入了
很多额外的部门。
如果我们想将此方法应用于显示更大的数字,请说
32 位,我们最终会希望,所涉及的部门会得到
真的有问题。
所以方法一不实用,所以很少使用。
方法 2:除以 const 10
处理从右到左的数字似乎违反直觉
因为我们的目标是首先显示最左边的数字。但是当你即将
发现,它工作得很好。
通过将数字 (65535) 除以 10,我们得到一个商 (6553),它将
成为下一步的红利。我们还得到一个余数 (5)
还不能输出,所以我们必须保存在某个地方。堆栈是一个
方便的地方。
通过将上一步 (6553) 的商除以 10,我们得到
商 (655) 将成为下一步的股息。我们也得到
我们还不能输出的余数(3),所以我们必须保存它
某处。堆栈是一个方便的地方。
通过将上一步 (655) 的商除以 10,我们得到
商 (65) 将成为下一步的股息。我们也得到
一个我们还不能输出的余数(5),所以我们必须保存它
某处。堆栈是一个方便的地方。
通过将上一步 (65) 的商除以 10,我们得到
商 (6) 将成为下一步的被除数。我们也得到
一个我们还不能输出的余数(5),所以我们必须保存它
某处。堆栈是一个方便的地方。
将上一步(6)的商除以10,我们得到
一个商 (0),表示这是最后一个除法。我们也得到
我们可以立即输出为字符的余数 (6),但是
避免这样做被证明是最有效的,所以和以前一样,我们将
将其保存在堆栈中。
此时堆栈包含我们的 5 个余数,每个都是一个数字
[0,9] 范围内的数字。由于堆栈是 LIFO(后进先出),因此
我们将POP first 的值是我们要显示的第一个数字。我们使用一个
使用 5 个 POP 的单独循环来显示完整的数字。但在实践中,
因为我们希望这个例程也能够处理具有
少于 5 位,我们将在数字到达时对其进行计数,然后再计算
许多POP的。
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
第二种方法没有第一种方法的缺点:
- 因为我们在商变为零时停止,所以从来没有任何问题
带有丑陋的前导零。
- 分频器是固定的。这很容易。
- 将此方法应用于显示较大的数字和
这正是接下来要发生的事情。
显示 DX:AX 中保存的无符号 32 位数字
在8086 上,需要级联 2 个除法来将 32 位值除以
DX:AX 10.
第一除法除以高股息(以 0 扩展)产生高
商。第 2 师划分低股息(与
第一次除法的余数)产生低商。这是剩下的
从我们保存在堆栈中的第二个分区开始。
要检查DX:AX 中的双字是否为零,我已经OR-ed 两半
注册。
我选择输入sentinel,而不是计算数字,需要寄存器
在堆栈上。因为这个哨兵得到一个数字(10),任何数字都不能
有 ([0,9]),它可以很好地确定显示循环何时必须停止。
除了这个sn-p和上面的方法2类似。
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
显示 DX:AX 中保存的带符号的 32 位数字
程序如下:
首先通过测试符号位来判断有符号数是否为负。
如果是,则取反数字并输出一个“-”字符,但要注意不要
在进程中销毁DX:AX中的号码。
sn-p 的其余部分与无符号数相同。
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
对于不同的数字大小,我需要单独的例程吗?
在您需要偶尔显示AL、AX 或DX:AX 的程序中,您可以
只需包含 32 位版本并使用下一个小wrappers
尺寸:
; 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
...
或者,如果您不介意破坏 AX 和 DX 寄存器,请使用
这个失败的解决方案:
; 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
...