4

私は、私のプログラムの 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 $'

( Ralph Brown の interrupt listを使用して) DOS API を検索しましたが、数値を出力する関数が 1 つも見つかりませんでした! 見逃してしまったのですが、どうすればよいですか?

DX:AX数値を 10 進数で表示したい。

4

1 に答える 1

11

DOS が数値を直接出力する機能を提供していないことは事実です。
最初に数値を自分で変換してから、テキスト出力関数の 1 つを使用して DOS に表示させる必要があります。

AX に保持されている符号なし 16 ビット数の表示

数値を変換する問題に取り組むときは、数値を構成する数字が互いにどのように関連しているかを確認することが役立ちます。
数値 65535 とその分解を考えてみましょう。

(6 * 10000) + (5 * 1000) + (5 * 100) + (3 * 10) + (5 * 1)

方法 1 : 10 のべき乗で除算する

左から右に番号を処理することは、抽出した直後に個々の数字を表示できるため便利です。

  • 数値 (65535) を10000で割ると、すぐに文字として出力できる 1 桁の商 (6) が得られます。また、次のステップで被除数になる剰余 (5535) も取得します。

  • 前のステップの余り (5535) を1000で割ると、すぐに文字として出力できる 1 桁の商 (5) が得られます。また、次のステップで被除数となる剰余 (535) も取得します。

  • 前のステップ (535) の余りを100で割ると、1 桁の商 (5) が得られ、すぐに文字として出力できます。次のステップで被除数となる余り (35) も取得します。

  • 前のステップ (35) の剰余を10で割ると、1 桁の商 (3) が得られ、これをすぐに文字として出力できます。また、次のステップで被除数となる剰余 (5) も取得します。

  • 前のステップ (5) の剰余を1で割ると、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] の範囲の 1 桁の数字です。スタックは LIFO (Last In First Out) であるため、最初に表示する値POPは、表示したい最初の桁です。POP完全な数を表示するには、5 を含む別のループを使用します。しかし実際には、このルーチンで 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

この 2 番目の方法には、最初の方法の欠点はありません。

  • 商がゼロになると停止するため、見苦しい先行ゼロの問題はまったくありません。
  • 仕切りは固定です。それは簡単です。
  • この方法をより大きな数値の表示に適用するのは非常に簡単で、まさに次のステップです。

DX:AXに保持されている符号なし32ビット数の表示

では、32 ビット値を10で除算するために、2 つの除算のカスケードが必要です DX:AX
最初の除算は、高い被除数 (0 で拡張) を除算し、高い商を生成します。2 番目の除算は、低い被除数 (1 番目の除算の余りで拡張) を割り、低い商を生成します。スタックに保存するのは、2 番目の除算の残りです。

dwordDX:AXがゼロかどうかを確認するために、OR両方の半分をスクラッチ レジスタに入力しました。

レジスタを必要とする数字を数える代わりに、番兵 をスタックに置くことにしました。このセンチネルは、どの桁も持つことができない値 (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

数値サイズごとに個別のルーチンが必要ですか?

ALAX、またはをときどき表示する必要があるプログラムでDX:AXは、32 ビット バージョンを含めて、小さいサイズの次の小さなラッパーを使用できます。

; 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
    ...
于 2017-08-27T10:41:55.510 に答える