Сборник по задачам и примерам Assembler

pregnancy week picture Новости для туристов и еще.


Вывод чисел на консоль

В этом разделе мы рассмотрим алгоритмы обратного преобразования чисел — из внутреннего двоичного представления в число в символьном виде, формат записи которого соответствует правилам требуемой системы счисления. Необходимо предупредить читателя, что рассмотрение обратного преобразования не будет симметричным рассмотренному выше прямому преобразованию. И в подтверждение этому начнем обсуждение проблемы вывода чисел на консоль с алгоритма преобразования шестнадцатеричных чисел в символьное представление.
Вывод шестнадцатеричных чисел
Умение работать с шестнадцатеричными числами — необходимое условие успешного программирования на низком уровне. Шестнадцатеричные числа по сравнению с двоичными являются более естественными для анализа внутреннего представления информации в компьютере. Вспомним, что каждый байт — это совокупность двух тетрад, а диапазон значений, представимых одной тетрадой, как раз совпадает с диапазоном значений, которые может принимать однозначное шестнадцатеричное число. Поэтому сам процесс преобразование шестнадцатеричных чисел в символьное представление особого труда не представляет. Например, алгоритм вывода на консоль содержимого одного байта состоит в выделении некоторым способом значений его младшей и старшей тетрад и дальнейшее их преобразование в символьное представление. Если нужно вывести на консоль символьное представление более чем одного байта, то процесс выделения тетрад и их преобразования выполняется последовательно необходимое количество раз.
В качестве полезной иллюстрации алгоритма преобразования шестнадцате-ричной информации в символьное представление рассмотрим макрокоманду SHOW, которая преобразует содержимое одного из четырех регистров — AL, АН, АХ, ЕАХ в символьное шестнадцатеричное представление. Этот макрос является универсальном средством, которое позволяет осуществить «подглядывание» за содержимым регистра или области памяти динамически, во время выполнения программы. С помощью этого макроса можно визуализировать содержимое любого
из доступных регистров или области памяти длиной до 32 бит. Для этого доста точно лишь переслать содержимое нужного объекта (регистра или ячейки памяти} с учетом его размера в один из регистров AL, АН, АХ, ЕАХ. Имя одного из этих регистров указывается затем в качестве фактического аргумента макрокоманды SHOW Второй аргумент этого макроса — позиция на экране. Задавая определенные значения, мы можем судить о том, какая именно макрокоманда SHOW сработала. Еще одна немаловажная особенность данного макроса состоит в его возможности работать как в реальном, так и защищенном режимах. Распознавание текущего режима работы микропроцессора выполняется автоматически. Проверить работу данного макроопределения вы можете с помощью следующей программы.
Ниже приведены фрагменты текста макрокоманды SHOW. Полный текст этой макрокоманды имеется в ПРИМЕРе.

:show.inс
макроопределение для визуализации регистров AL. АН. АХ. ЕАХ
;:на входе:
:;агд_п - имя одного из регистров AL. АН, АХ. ЕАХ
;;п_ро2 - номер позиции на экране, по умолчанию - 1000
Show MACRO a rg_n.n_poz:=<1000>
LOCAL mai n_part.di sp.pause.tempi ate,VideoBuffer.pjnode.ml.m2
:;переход на начало блока команд, чтобы избежать выполнения данных
jmpmain_part ;:некоторые константы и переменные
main_part: :начало блока команд
сохранение в стеке используемых регистров: ЕАХ. ЕВХ. ЕСХ. EDX. EDI. DS. ES
push cs
pop ds :в bx - адрес таблицы-шаблона (для xlat)
lea bx.cs:tempi ate
xor ex.ex :очистка сх
:начало блока определения того, какой регистр был передан макросу IFIDNI <al>.<&arg_n> :если аргумент=а1 или AL. ?reg8bit=TRUE установка флага 8-битового регистра
mov ah.al ENDIF
;передан не al или AL
IFIDNI <ah>.<&arg_n> :если аргумент-ah или АН.
?reg8bit=TRUE -.установка флага 8-битового регистра
ENDIF
;передан не АН или ah
IFIDNI <ax>.<&arg_n> ;если аргумент равен ах или АХ,
?regl6bit=TRUE -.установка флага 16-битового регистра
ENDIF
;передан не ah. АН ,ах или АХ .¦ _ ,;.
обработка содержимого регистров AL. АН, АХ. ЕАХ IF (?reg8bit) -.если передан а! или ah -:"'"" "' push eax -.-'- ¦
and ah. Of Oh; обращение к старшей четверке битоВ'.-ah' shr ax. 12 -.сдвиг битов в начало (16-4=12) xlat трансляция таблицы-шаблона помещение символа из al в edi ¦¦j:\--.i ¦.;¦,.> -«-.iY ¦ ';¦' mov di.ax ¦ ¦ ,k ,-,,
shl di .8 inc ex pop eax and ax.OfOOh shr ax.8 xlat
or di.ax
shl edi. 16 . , ...
inc ex ENDIF ¦ IF (?regl6bit) ;если передан ах или ах
-.начало обработки значения регистра АХ push eax
-.обращение к старшей четверке битов ах .
and ax.OfOOOh .',.
shr ax.12 ;сдвиг битов в начало (16-4=12) xlat трансляция таблицы-шаблона ......
помещение символа из а! в старшую тетраду старшей половины ЕЩ . ",, mov di ,ax *i
shl edi.8
inc ex
pop eax push eax обращение ко второй четверке битов ах
and ax.OfOOh
' shr ax,8 ;сдвиг битов в начало (16-(4+4)=8)
xlat -.трансляция таблицы-шаблона
.помещение очередного символа в младшую тетраду старшей половины EDI
or di.ax
shl edi.8
inc ex ' ' . '' '".¦;;1
pop eax .
push eax
and ax.OfOh;обращение к третьей четверке битов в АХ -. v.'v
shr ax.4 ;сдвиг битов в начало (16-(4+4+4)=4) ¦xlat трансляция таблицы-шаблона
or di.ax -.помещение очередного символа в EDI
! shl edi.8
i nc ex
pop eax
and ax.Ofh обращение к младшей четверке битов АХ
xlat трансляция таблицы-шаблона
or di.ax помещение очередного символа в EDI
inc ex ENDIF
IF (?reg32bit) ;если передан ЕАХ или ЕАХ ;начало обработки значения регистра ЕАХ аналогично АХ
ENDIF
;вывод на экран результата с учетом режима работы микропроцессора результат - в паре EDX:ЕВХ. количество цифр - в СХ
:.........
ENDM

Вывод целых десятичных чисел из диапазона 0..99

Выше упоминалось, что для значений из диапазона 0..99 взаимное преобразование между символьной десятичной и двоичной формами может производиться командами умножения и деления двоично-десятичных (BCD-чисел) — ААМ и AAD.

;prg06_07.asm - ввод с консоли десятичного числа из диапазона 0..99
:и обратный его вывод на консоль.
buf_Oahstruc
len_bufdb 3 ;длина buf_0ah
len_in db 0 .действительная длина введенного слова (без учета Odh)
bufjn db 3 dup (20h) :буфер для ввода (с учетом Odh) ends .data
bufbuf_0ah<> adr_bufdd buf ' .code
:вводим 2 символа с клавиатуры, контроль на допустимые значения не делаем
ldsdx.adr_buf
mov ah.Oah
int 21h
хог ах.ах
emp buf .len_in,2 .-сколько чисел введено реально?
jne ml
mov ah.buf .bufjn ml: mov al.buf,buf_in+l
andax.OfOfh преобразование в неупакованное десятичное представление
add :в AL двоичный эквивалент исходного двузначного десятичного значения ;вывод на консоль содержимого AL
аат
mov dx.ax
or dx.03030h
rol dx.8 :выводим старшую цифру
mov ah,2
int 21h
rol dx.8 :выводим младшую цифру
int 21h

Для преобразования с целью последующего вывода на консоль больших двоичных значений можно использовать два способа: путем деления по модулю 10 (диапазон значений не ограничен) и с помощью сопроцессора (0..1018-1).

Вывод целых десятичных чисел из диапазона от 0 до бесконечности

Для вывода двоичных значений из диапазона от 0 до бесконечности используется способ, в основе которого лежит получение остатков при последовательном делении исходного значения на 10.
В основе алгоритма лежит положение о том, что цифры (...U2U,U0) десятичного представления начиная с младшей получаются последовательным делением исходного двоичного значения и на 10:
U0=u mod 10; U1Lu/10j mod 10; U2 41u/10j /10J mod 10 и т. д., до тех пор пока после очередного деления делимое не окажется равным нулю: L.-iLu/10j/10j..J=0 Здесь символы L и J обозначают целую часть частного, округленного в меньшую
сторону.
Почему в отличие от алгоритмов ввода с консоли для обратного преобразова ния нет такого разнообразия способов? Это объясняется особенностью командь деления DIV микропроцессора, которая используется в описанном выше алгорит ме для получения частного и остатка. Ее требование к соотношению значенш делимого и делителя — размер частного должен быть в два раза меньше делимо го. В противном случае возникает исключение #0Е (ошибка деления) и програм ма аварийно завершается.
Исходя из этих условий нам ничего не остается, кроме как воспользоватьс программой беззнакового деления значения размером N байт на значение разме ром 1 байт. Она была рассмотрена в главе 1, посвященной целочисленным ариф метическим операциям. Для удобства использования эту программу мы офор мим в виде макрокоманды.

:prg06_08.asm - программа вывода целых десятичных чисел из диапазона О..оо.
;Вход: многобайтное двоичное число для преобразование в области памяти bin_dd.
:Выход: вывод десятичного числа из диапазона 0..<ю на экран.
:
div_unsign_N_l_I macro u.N.v.w.r
:div_unsign_N_l_I - макрокоманда деления N-разрядного беззнакового целого
:на одноразрядное число размером 1 байт (порядок следования байтов - младший байт
:по младшему адресу (Intel)). См. главу 1 и дискету
endm .data string db 10 dup (0) ;пусть максимальное десятичное число состоит из 10 цифр
len_string-$-string adr string dd string b1n~dd label BYTE "dd Offffffffh 1 еп_Ы n_dd-$ - bi n_dd ten*db To remainder dw 0 .code
значение для преобразования должно быть в памяти
les di,adr_string строка с десятичными символами
eld обработка в прямом направлении
continue:
di v_unsign_N_l_I bin_dd.1en_bin_dd.ten.Ыn_dd.remainder
mov ax.remainder
or al.30h :преобразуем в символьное представление
stosb сохраняем в string очередную десятичную цифру
inccx {подсчитываем количество цифр
cmpbinjjd.0
jne continue :вывод на консоль с конца строки
mov ah,2
std
mov si .di
dec si ml: "lodsb
mov dl ,al
Int 21h
loop ml

В данной программе преобразованию подвергается значение в памяти. Причем мы в качестве исходного двоичного значения задали максимально возможное беззнаковое число размером в двойное слово. Результат преобразования — 4 294 967 295, что полностью сходится с ожидаемым десятичным значением. Но задавать исходные значения в памяти не всегда удобно, хотелось бы, чтобы можно было подвергать преобразованию значения прямо из регистров процессора. Для такого типа преобразований (значений в регистрах процессора) лучше подойдет способ с использованием сопроцессора. Рассмотрим его.

Вывод целых десятичных чисел из диапазона 0..999 999 999 999 999 999

Этот способ вывода основан на возможности сопроцессора работать с упакованными десятичными числами. Выше мы уже рассматривали преобразование десятичных чисел в двоичное представление с использованием этой возможности. Система команд сопроцессора содержит команду FBSTP, которая сохраняет десятичное число из вершины стека сопроцессора в области памяти с одновременным преобразованием этого числа в формат десятичного числа. Область памяти, в которую происходит сохранение, должна быть описана директивой DT. Важно отметить, что команда FILD, с помощью которой вы будете помещать целое число в сопроцессор для дальнейшего преобразования, трактует целые числа как числа со знаком. Поэтому попытка задать целое число в виде Offffh (с единичным старшим разрядом операнда) приведет к тому что в стек сопроцессора будет помешено значение со всеми вытекающими отсюда последствиями для результата преобразования.

;prg06_09.asm - программа вывода целого десятичного числа
:из диапазона 0..999 999 999 999 999 999 на экран.
¦.Вход: выводимое значение - в поле string_bin_dword.
¦.Выход: вывод десятичного числа из диапазона 0. .999 999 999 999 999 999 на экран.
.data
:поле string_bin_dword содержит выводимое значение - с помощью идентификаторов.
:вводимых директивой label, это значение может трактоваться как значение
различной разрядности:
string_bin_byte label byte
string_bin_word label word
string_bin_dword label dword
string_bin_qword dq 0de0b6b3a763ffffh :зададим максимально возможное
¦.для сопроцессора двоичное целое со знаком
;в string_pack исходное значение из string bin_dword в упакованном десятичном формате string_pack dt О len_string_pack=$-string_pack adr_string_pack dd string_pack string db 20 dup (0) максимальный результат состоит из 18 десятичных цифр
len_string-$-string adr_stringdd string
.code
:.........преобразуем bin->dec
finit
fild string_bin_qword ;заносим в сопроцессор двоичное целое число fbstp string_pack извлекаем упакованное десятичное :.........распакуем........................................
Ids si.adr_string_pack
add si.len_string_pack-2 ;на конец string_pack (18 упак. дес. цифр)
les di.adr_string
mov ex.9 :9 пар упакованных десятичных цифр
cycl: xorax.ax
std :string_pack обрабатываем с конца
lodsb :в al очередные 2 упакованные десятичные цифры
¦»;распаковываем - ah-младшая. al-старшая
shi ax.4
rol al.4
ог ах.З0З0п треобразуем в символьное представление
xchg ah.al iah-младшая, al-старшая
eld ;в string записываем с начала
stosw
loop cycl :.........выводим на консоль...............................
mov bx.l -.стандартный дескриптор - экран
mov cx.len_string
Ids dx.adr_string {формируем указатель на строку string
mov ah.40h ;номер функции DOS
int 21h :выводим
jc exit :переход в случае ошибки

Вывод вещественных чисел

Последнее преобразование, которое мы рассмотрим в этом разделе, — преобразование вещественного значения в вид, пригодный для его визуализации на экране консоли. Ниже приведены только те фрагменты программы, которые относятся непосредственно к преобразованию. Полный текст программы находится в ПРИМЕРе.

;prg06_10.asm - программа вывода вещественного числа короткого формата (32 бита).
;Вход: выводимое значение - в поле float32.
:Выход: вывод вещественного числа короткого формата на экран
.data
dec_bin_mant32dt 0 ;мантисса в двоично-десятичном представлении
dec_bin_har32 dt 0 характеристика в двоично-десятичном представлении
cwr dw 0 переменная для сохранения состояния per. cwr
ten dw 10 ;константа, равная 10
float32dd 1.2345678el2 значение вещ. числа размером в 32 бита для вывода
mant32 dd 0 :мантисса в двоичном представлении .
har32 dd 0 характеристика - вещ. формат в двоичном представлении
int_har32 dd 0 характеристика - целое в двоичном представлении
number db 0
char db 0
cursor_column db 0
cursorjine db 0
number_of_digits db 9
flag db 0
.code
next_cursor_column ргос .-процедура сдвига курсора на одну позицию вправо
обязательно наличие в программе процедур::read_cursor_position, set_cursor_position
next_cursor_column endp
set_cursor_position ргос .'процедура позиционирования курсора
set_cursor_position endp
read_cursor_position ргос процедура определения текущей позиции курсора
read_cursor_position endp
print_charргос процедура вывода символа с учетом цвета
;.........
printjrhar endp
positivejiar ргос
:выделение мантиссы из короткого формата (32бита) и ее преобразование
:в двоично-десятичный формат (для положительной характеристики
результат в st(0))
fimul ten
sub int_har32,6 lab_p_h: fidiv ten
cmp int_har32.0
dec int_har32 jg lab_p_h ret I positivejiar endp
negativejiar ргос
:выделение мантиссы из короткого формата (32 бита) и ее преобразование
;в двоично-десятичный формат (для отрицательной характеристики результат в st(0))
fidiv ten
sub int_har32.7 lab_n_h: fimul ten
cmp int_har32.0
inc int_har32
jl lab_n_h
ret
negative_har endp fprint32 proc :вывод вещественного числа (32 бита) в десятичном виде
pusha
установка размера мантиссы в 24 бита fstcw cwr and cwr.1111000011111111b
or cwr.ldew cwr
fid float32:загрузка 32-битного числа в стек сопроцессора fxtract выделение мантиссьКБШ и характеристики^!) fstp mant32 запоминаем мантиссу fist har32 '.запоминаем характеристику щеревод двоичн. характеристики в дес. характеристику fldlg2 -.загрузка десят. лог. двух fimul har32 -.умножение двоичной хар-ки на 1од10(2) frndint -.округление fistp int_har32 сохранение десетяч. характеристики
fild int_har32
fbstp dec_bin_har32 сохранение двоично-десятичного значения характеристики
:выбор процедуры по выделению мантиссы fid float32 cmp har32.0 jge easel
call negativejiar :вызвать процедуру преобразования мантиссы, -.если хар-ка отрицательная
jmp end_case
I^casel: call positivejiar -.вызвать процедуру преобразования мантиссы, -.если хар-ка положительная end_case-. fbstp dec_bin_mant32 сохранение двоично-десятичного представления мантиссы ;вывод на экран вещественного числа lea si.dec_bin_mant32 add si.9 mov- a "I. [si] : вывод знака числа cmp al .0 je zero mov char."-" call print_char call next_cursor_column
:данный фрагмент пропускает байты с нулевым содержимым до первого байта ;со значащей цифрой zero: dec si
dec number_of_digits mov al.[si] cmp a 1.0
jne first_zero ;найден первый байт со значением, отличным от нуля jmp zero ;байт имеет нулевое значение - продолжаем поиск ; просмотр полубайтов первого найденного байта с ненулевым значением first_zero: and al ,11110000b cmp a 1,0 je second_digit :если старший полубайт байта равен нулю, начинаем вывод
:со второго байта jmp first_digit :если старший полубайт байта не равен нулю, начинаем вывод
:с первого байта :начало цикла вывода мантиссы print_digits: dec si
dec number_of_digits :индекс выводимого байта mov al.[si]
:вывод первого полубайта, содержащего цифру first_digit: andal ,11110000b shr al.4 add al.30h mov char.al
call print_char ,
call next_cursor_column
;если выводимая цифра первая, то выводим после нее точку cmp flag.0 jne second_digit mov char."." call print_char ca11 next_cursor_column inc flag
:вывод второго полубайта, содержащего цифру second_digit: mov al. [si] and al,00001111b add a1,30h mov char.al call print_char call next_cursor_column
.-если выводимая цифра первая, то выводим после нее точку cmp flag,0 jne nonfirst_digit mov char,"." call print_char call next_cursor_column inc flag
nonfi rst_digit: cmp number_of_d1g1ts.O jne print_digits mov flag,0
:вывод характеристики числа mov char,"E" call print_char call next_cursor_column lea si,dec_bin_har32
:вывод знака числа add si.9 mov al,[si] cmp al.O je printjiar mov char,"-" call print_char call next_cursor_column : значения характеристики print_har: sub si .9
mov al.[si] ;вывод первой цифры характеристики
and al.11110000b
shr al.4
add al.30h
mov char.al
call print_char
call next_cursor_col umn : вывод второй цифры характеристики
mov al.[s1]
and al.00001111b
add a1.30h
mov char.al
call print_char
call next_cursor_col umn
mov flag.0
fprint32 endp
main proc
\.........
call fprint32


Книжный магазин