确实,DOS 没有为我们提供直接输出数字的功能。
你必须先自己转换数字,然后让 DOS 显示它
使用文本输出功能之一。
显示 AX 中保存的无符号 16 位数字
当解决数字转换问题时,看看如何
组成数字的数字彼此相关。
让我们考虑一下数字 65535 及其分解:
(6 * 10000) + (5 * 1000) + (5 * 100) + (3 * 10) + (5 * 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 位,我们希望最终涉及的部门将得到
确实有问题。
所以方法1不切实际,因此很少使用。
方法2:除以const 10
处理从右到左的数字似乎违反直觉
因为我们的目标是首先显示最左边的数字。但当你即将
发现,它工作得很漂亮。
将数字 (65535) 除以10,我们得到一个商(6553)
成为下一步的红利。我们还得到余数 (5)
目前还无法输出,所以我们必须保存在某个地方。堆栈是一个
方便的地方这样做。
将上一步的商 (6553) 除以10, 我们获得
商(655)将成为下一步的被除数。我们还得到
我们还不能输出余数 (3),因此我们必须保存它
某处。堆栈是一个方便的地方。
将上一步的商 (655) 除以10, 我们获得
商 (65) 将成为下一步的被除数。我们还得到
我们还不能输出余数 (5),因此我们必须保存它
某处。堆栈是一个方便的地方。
将上一步 (65) 的商除以10, 我们获得
商 (6) 将成为下一步的被除数。我们还得到
我们还不能输出余数 (5),因此我们必须保存它
某处。堆栈是一个方便的地方。
将上一步 (6) 的商除以10, 我们获得
商 (0) 表示这是最后一次除法。我们还得到
余数 (6) 我们could立即输出为字符,but事实证明,不这样做是最有效的,所以像以前一样,我们将
将其保存在堆栈中。
此时堆栈保存着我们的 5 个余数,每个都是一个数字
[0,9] 范围内的数字。由于堆栈是 LIFO(后进先出),
重视我们将POP
first 是我们想要显示的第一个数字。我们使用一个
单独循环 5POP
的来显示完整的数字。但在实践中,
因为我们希望这个例程也能够处理具有
少于 5 位数字,我们将在数字到达时对其进行计数,然后再进行计算
许多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
第二种方法没有第一种方法的缺点:
- 因为当商变为零时我们就停止,所以永远不会有任何问题
带有丑陋的前导零。
- 分隔器是固定的。这很容易。
- 应用这种方法来显示更大的数字非常简单
这正是接下来发生的事情。
显示 DX:AX 中保存的无符号 32 位数字
On 8086 /questions/tagged/8086需要级联 2 个除法来将 32 位值除以DX:AX
by 10.
第 1 次除法除以高股息(以 0 扩展),产生高股息
商。第二部分划分低股息(扩展为
第一除法的余数)产生低商。这是剩下的
从我们保存在堆栈上的第二个部分开始。
检查双字是否在DX:AX
为零,我已经OR
-ed 两半都划伤了
登记。
我不需要计算数字,需要一个寄存器,而是选择放一个sentinel https://en.wikipedia.org/wiki/Sentinel_value在堆栈上。因为这个哨兵得到的值 (10) 是任何数字都无法得到的
有 ([0,9]),它可以很好地确定显示循环何时必须停止。
除此之外,此代码片段与上面的方法 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
正在进行中。
代码片段的其余部分与无符号数相同。
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
, or DX:AX
, 你可以
只需包含 32 位版本并使用下一点wrappers https://en.wikipedia.org/wiki/Wrapper_function对于较小的
尺寸:
; 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
and 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
...