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ビット数の表示
8086では、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
数値サイズごとに個別のルーチンが必要ですか?
AL
、AX
、またはをときどき表示する必要があるプログラムで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
...