gpt4 book ai didi

assembly - 用DOS显示数字

转载 作者:行者123 更新时间:2023-12-04 23:14:43 26 4
gpt4 key购买 nike

我受命编写一个显示我的线性地址的程序
程序的PSP。我写了以下内容:

        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's interrupt list
而且没有找到输出数字的单一功能!
我是否想念它,该怎么办?

我想以十进制显示 DX:AX中的数字。

最佳答案

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。(避免将此愚蠢的除以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),但是
避免这样做是最有效的,因此像以前一样
将其保存在堆栈上。


此时,堆栈中包含我们的5个余数,每个余数为一位
[0,9]范围内的数字。由于堆栈是LIFO(后进先出),因此
我们首先要 POP的值是我们要显示的第一位数字。我们使用
用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位数字

上,需要2级除法来划分32位值
DX:AX乘10。
1级除以高股息(扩展为0),得到高股息
商。第二部分除以低股息(以
1级除法的余数)得到低商。剩下的就是
从我们保存在堆栈中的第二个分区开始。

要检查 DX:AX中的双字是否为零,我已经将 OR的两个半部分从头开始
寄存器。

我选择输入 sentinel而不是计数数字,而需要注册
在堆栈上。因为此哨兵获得的值(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




是否需要针对不同号码大小的单独例程?

在需要偶尔显示 ALAXDX: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
...


另外,如果您不介意 AXDX寄存器的使用,
该解决方案:

; 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
...

关于assembly - 用DOS显示数字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45904075/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com