用DOS显示数字 [英] Displaying numbers with DOS
问题描述
我受命编写一个显示我的线性地址的程序 程序的PSP.我写了以下内容:
I was tasked to write a program that displays the linear address of my program's PSP. I wrote the following:
ORG 256
mov dx,Msg
mov ah,09h ;DOS.WriteStringToStandardOutput
int 21h
mov ax,ds
mov dx,16
mul dx ; -> Linear address is now in DX:AX
???
mov ax,4C00h ;DOS.TerminateWithExitCode
int 21h
; ------------------------------
Msg: db 'PSP is at linear address $'
我搜索了DOS api(使用 Ralph Brown的中断列表) 并没有找到输出数字的单一功能! 我是否想念它,该怎么办?
I searched the DOS api (using Ralph Brown's interrupt list) and didn't find a single function to output a number! Did I miss it, and what can I do?
我想用DX:AX
用十进制显示数字.
I want to display the number in DX:AX
in decimal.
推荐答案
确实,DOS没有为我们提供直接输出数字的功能.
您必须先自己转换数字,然后让DOS显示它
使用文本输出功能之一.
It's true that DOS doesn't offer us a function to output a number directly.
You'll have to first convert the number yourself and then have DOS display it
using one of the text output functions.
在解决数字转换问题时,有助于了解如何
组成数字的数字彼此相关.
让我们考虑数字65535及其分解:
When tackling the problem of converting a number, it helps to see how the
digits that make up a number relate to each other.
Let's consider the number 65535 and its decomposition:
(6 * 10000) + (5 * 1000) + (5 * 100) + (3 * 10) + (5 * 1)
方法1:除以10的幂
处理从左到右的数字很方便,因为它可以 允许我们在提取单个数字后立即显示它.
Method 1 : division by decreasing powers of 10
Processing the number going from the left to the right is convenient because it allows us to display an individual digit as soon as we've extracted it.
-
通过将数字(65535)除以 10000 ,我们得到一位数的商 (6)我们可以立即将其输出为字符.我们也有余数 (5535),它将在下一步成为股息.
By dividing the number (65535) by 10000, we obtain a single digit quotient (6) that we can output as a character straight away. We also get a remainder (5535) that will become the dividend in the next step.
通过将上一步(5535)的剩余部分除以 1000 ,我们得到 一个单数商(5),我们可以立即将其作为字符输出. 我们还得到了余数(535),将在下一步成为红利.
By dividing the remainder from the previous step (5535) by 1000, we obtain a single digit quotient (5) that we can output as a character straight away. We also get a remainder (535) that will become the dividend in the next step.
通过将上一步(535)的余数除以 100 ,我们得到 一个单数商(5),我们可以立即将其作为字符输出. 我们还得到了余数(35),将在下一步成为红利.
By dividing the remainder from the previous step (535) by 100, we obtain a single digit quotient (5) that we can output as a character straight away. We also get a remainder (35) that will become the dividend in the next step.
通过将上一步(35)的余数除以 10 ,我们得到 一个单数商(3),我们可以立即将其作为字符输出. 我们还得到了余数(5),将在下一步成为红利.
By dividing the remainder from the previous step (35) by 10, we obtain a single digit quotient (3) that we can output as a character straight away. We also get a remainder (5) that will become the dividend in the next step.
通过将上一步(5)的余数除以 1 ,我们得到 一个单数商(5),我们可以立即将其作为字符输出. 在这里,余数将始终为0.(避免将此傻除以1 需要一些额外的代码)
By dividing the remainder from the previous step (5) by 1, we obtain a single digit quotient (5) that we can output as a character straight away. Here the remainder will always be 0. (Avoiding this silly division by 1 requires some extra code)
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
尽管这种方法当然会产生正确的结果,但是它有一些 缺点:
Although this method will of course produce the correct result, it has a few drawbacks:
-
考虑较小的数字255及其分解:
Consider the smaller number 255 and its decomposition:
(0 * 10000) + (0 * 1000) + (2 * 100) + (5 * 10) + (5 * 1)
如果我们使用相同的5步过程,则会得到"00255".那些2领先 零是不可取的,我们将不得不包括额外的指令来获取 摆脱它们.
If we were to use the same 5 step process we'd get "00255". Those 2 leading zeroes are undesirable and we would have to include extra instructions to get rid of them.
分频器随每个步骤而变化.我们必须将分频器列表存储在 记忆.动态计算这些分频器是可能的,但是引入了 很多额外的部门.
The divider changes with each step. We had to store a list of dividers in memory. Dynamically calculating these dividers is possible but introduces a lot of extra divisions.
如果我们想将此方法应用于显示更大的数字,请说 32位,我们最终希望将涉及的部门划分为 真的有问题.
If we wanted to apply this method to displaying even larger numbers say 32-bit, and we will want to eventually, the divisions involved would get really problematic.
因此方法1不切实际,因此很少使用.
So method 1 is impractical and therefore it is seldom used.
处理从右到左的数字似乎违反直觉 因为我们的目标是首先显示最左边的数字.但是随着你即将 找出来,它很漂亮.
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.
-
通过将数字(65535)除以 10 ,我们得到的商(6553)将 成为下一步的红利.我们还得到了余数(5) 暂时还无法输出,因此我们必须将其保存在某个地方.堆栈是一个 方便的地方.
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.
通过将上一步(6553)的商除以 10 ,我们得到 商(655),它将在下一步成为红利.我们也得到 剩下的(3)我们还不能输出,所以我们必须保存它 某处.堆栈是方便的放置位置.
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.
通过将上一步(655)的商除以 10 ,我们得到 商(65),它将在下一步成为红利.我们也得到 剩下的(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.
通过将上一步(65)的商除以 10 ,我们得到 商(6)将在下一步成为红利.我们也得到 剩下的(5)我们还不能输出,所以我们必须保存它 某处.堆栈是方便的放置位置.
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.
通过将上一步(6)的商除以 10 ,我们得到 表示这是最后除法的商(0).我们也得到 我们可以可以直接输出为字符的余数(6),但是 避免这样做是最有效的,所以像以前一样 将其保存在堆栈中.
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.
此时,堆栈中包含5个余数,每个余数为一位
[0,9]范围内的数字.由于堆栈是LIFO(后进先出),因此
我们首先要POP
的值是我们要显示的第一个数字.我们使用
用5个POP
的单独循环显示完整的数字.但实际上
因为我们希望这个例程也能够处理具有
少于5位数字,我们将在数字到达时对其进行计数,然后再进行计算
许多POP
.
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.
在 8086 的问题上,由2个部分组成的级联是需要将32位值除以
DX:AX
乘10.
1级除以高分红(扩展为0),得到高分红
商.第二部分除以低股息(以
1级除法的余数)得到低商.剩下的就是
来自我们保存在堆栈中的第二个分区.
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.
要检查DX:AX
中的dword是否为零,我已将OR
的两个半部分从头开始进行了编辑
注册.
To check if the dword in DX:AX
is zero, I've OR
-ed both halves in a scratch
register.
我选择放置一个前哨,而不是计算数字而不需要注册. 在堆栈上.因为此哨兵获得的值(10)不可能有数字 具有([0,9]),可以很好地确定何时必须停止显示循环.
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.
除了此片段与上面的方法2相似.
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
显示DX:AX中保存的签名的32位数字
过程如下:
Displaying the signed 32-bit number held in DX:AX
The procedure is as follows:
首先通过测试符号位来找出符号号是否为负.
如果是,则取反数字并输出-"字符,但请注意不要
销毁DX:AX
中的数字.
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
我需要针对不同数字大小的单独例程吗?
在需要偶尔显示AL
,AX
或DX:AX
的程序中,您可以
只需包含32位版本,然后使用较小的包装器
大小:
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
...
或者,如果您不介意AX
和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
...
这篇关于用DOS显示数字的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!