Ответ 1
Это правда, что DOS не предлагает нам функцию для вывода номера напрямую.
Вы должны сначала преобразовать номер самостоятельно, а затем отобразить DOS
используя одну из функций вывода текста.
Отображение 16-разрядного номера без знака, содержащегося в AX
При решении проблемы преобразования числа это помогает увидеть, как
цифры, составляющие число, относятся друг к другу.
Рассмотрим число 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 (Last In First Out),
значение, которое мы будем 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
Этот второй метод не имеет ни одного из недостатков первого метода:
- Поскольку мы останавливаемся, когда фактор становится нулевым, никогда не возникает никаких проблем с уродливыми начальными нулями.
- Делитель исправлен. Это достаточно легко.
- Очень просто применить этот метод для отображения больших чисел и это то, что будет дальше.
Отображение 32-разрядного номера без знака, содержащегося в DX: AX
В 8086 каскад из 2 деления необходимы для деления 32-битного значения в
DX:AX
на 10.
1-й дивизион делит высокий дивиденд (расширен с 0), что дает высокий
фактор. Второе подразделение делит низкий дивиденд (расширенный с помощью
остаток от 1-го дивизиона), давая низкий коэффициент. Это остаток
от 2-го деления, которое мы сохраняем в стеке.
Чтобы проверить, равен ли dword в 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
Отображение подписанного 32-разрядного номера, содержащегося в DX: AX
Процедура такова:
Сначала выясните, является ли подписанное число отрицательным, проверив знаковый бит.
Если это так, то отрицайте число и выводите символ "-", но будьте осторожны, чтобы не
уничтожить число в DX:AX
в процессе.
Остальная часть фрагмента такая же, как и для беззнакового числа.
test dx,dx ;Sign bit is bit 15 of high word
jns .a ;It 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
...
; 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
...