Преобразование чисел
Внутри каждой большой задачи сидит маленькая,
пытающаяся пробиться наружу.
Закон больших чисел Хоара (Прикладная Мерфология)
В предыдущей главе мы рассмотрели решение проблемы обмена данными с консолью. Данные, вводимые с консоли и выводимые на нее, кодируются операционной системой в соответствии с текущей таблицей кодировки. Отдельный предмет обсуждения при этом — процесс ввода-вывода числовой информации. В каждом языке программирования он реализован по-своему. Одна из целей, к которой стремятся разработчики компиляторов, — по возможности сделать этот процесс прозрачным для программиста. Язык ассемблера в отличие от языков высокого уровня не обладает средствами такого прозрачного ввода-вывода числовой информации. Но в этом и состоит его достоинство, так как при определенном опыте и квалификации программиста появляется хорошая возможность повышения эффективности конечного кода.
Для каждой конкретной задачи преобразование чисел между различными представлениями, допустимыми компьютером, может быть выполнено несколькими способами. Для эффективного решения задач обработки числовой информации программист должен знать эти способы и уметь выбрать наиболее подходящий из них для решения конкретной проблемы. Сразу следует отметить, что эта тема далеко не нова. Многие источники, посвященные ассемблеру, с той или иной степенью подробности рассматривают проблему преобразования чисел. И хотя здесь трудно придумать что-то новое, эта тема заслуживает того, чтобы ей было уделено место в книге, посвященной вопросам прикладного программирования па ассемблере. В данной главе сделана попытка систематизированно рассмотреть различные существующие подходы к решению проблемы ввода-вывода числовой информации и ее преобразованию во внутреннее представление в компьютере.
Начнем с того, что вспомним урок 2 «Архитектура персонального компьютера» учебника, где приведена классификация типов данных, допустимых микропроцессором Pentium III. Для нашего изложения важно то, что они делятся на две большие группы — данные целочисленного и вещественного типов. Причем целочисленные данные можно разделить на две подгруппы: двоичные и двоично-десятичные (BCD-числа). Исходя из этого постараемся сформулировать направления преобразований числовой и символьной информации, востребованные на практике.
При обмене с консолью:
вещественного числа; дробное шестнадцатеричное число в символьном виде «-> внутреннее представление в виде вещественного числа; дробное двоичное число в символьном виде <-> внутреннее представление в
виде вещественного числа.
Взаимное преобразование между внутренними представлениями: двоичное число <-» двоично-десятичное число;
двоично-десятичное число <-» вещественное число; двоичное целое число <-> вещественное число.
Рассмотрим основные способы выполнения некоторых из этих видов преобразований. В своих рассуждениях будем предполагать, что числа положительные. Если вы внимательно изучили материал уроков 6 и 8 учебника, а также главы 1 этой книги, посвященной программированию арифметических операций над двоичными и двоично-десятичными числами, то вы легко сможете дополнить приведенные ниже программы возможностью учета знака при выполнении соответствующих преобразований.
Ввод чисел с консоли
В этом разделе разберем способы преобразования десятичных целых и вещественных чисел, вводимых с консоли в символьном виде, в соответствующее им внутреннее двоичное представление. Начнем с преобразования целых десятичных чисел. Заметим, что этот вид преобразования является наиболее востребованным на практике. Далее будет обсуждена проблема преобразования вещественных чисел.
Выбор способа преобразования десятичных целых чисел из символьного во внутреннее двоичное представление и обратно определяется диапазоном возможных исходных значений. Разберем два способа.
Ввод целых десятичных чисел из диапазона 0..99
Для значений из диапазона 0..99 взаимное преобразование между символьной десятичной и двоичной формами может производиться командами умножения и деления двоично-десятичных (BCD-чисел) — ММ и AAD.
:prg06_01.asm - программа ввода с консоли двоичного числа из диапазона 0..99
:в десятичном символьном представлении
;Вход: число в десятичной системе счисления, вводимое в символьном виде с клавиатуры.
;Выход: двоичное число в регистре А1.
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 символа с клавиатуры, контроль на допустимые значения не делаем
Ids dx.adr__buf
nrav ah.Oah
int 21h
xor ax.ax
cmp buf .lenjn.2 ;сколько чисел введено реально?
jneml
mov ah.buf.buf_in ml: mov al,buf.buf_in+l
andax.0f0fh преобразование в неупакованное десятичное представление
aad :в al двоичный эквивалент исходного двузначного десятичного значения
fbld string_pack :помещаем в стек сопроцессора 'fistp string_bin ;и извлекаем эквивалентное двоичное представление в поле string_bin '¦.........
Приведенная программа преобразует любое значение из диапазона 0..1018-!. Интересно отметить количественное значение максимальной двоичной величины, соответствующее верхней границе диапазона, — это +0de0b6b3a763ffffl6. Запомните его, оно пригодится нам при рассмотрении обратного преобразования Для вывода на консоль — из двоичного в десятичное представление. Извлечь значение нужной разрядности можно, если ввести директивой label соответствующие идентификаторы в исходный текст программы (что и сделано в нашем сегменте кода):
Ввод целых десятичных чисел из диапазона 0..4 294 967 295
Если исходное значение выходит за диапазон 0..99, то здесь следует иметь в виду возможность возникновения ситуации, при которой значение вводимого десятичного числа превышает диапазон, допустимый форматами типов целочисленных данных, поддерживаемых, в частности, арифметическими командами микропроцессора. Для Pentium III это 8, 16 и 32 бита. Допустимые диапазоны значений для этих форматов (числа без знака):
Как видите, максимальное число не такое уж и большое. Поэтому мы столько внимания уделили работе с числами большой размерности в главе 1, посвященной программированию арифметических операций. В ней данные большой размерности просто описывались в сегменте данных без какого-либо намека на возможность их ввода с консоли или отображения на ней. В этом разделе мы постараемся ликвидировать этот недостаток, что несомненно поднимет привлекательность для читателя того и другого материала. Но вначале мы рассмотрим способы преобразования значений, которые укладываются в указанные выше диапазоны. Для этого можно предложить два способа преобразования в символьном представлении десятичных чисел, вводимых с консоли: с использованием деся-
точного полинома и с использованием возможностей сопроцессора по обработке
данных.
В основе способа с использованием десятичного полинома лежит возможность представления десятичного числа суммой произведений на степени числа 10 составляющих его десятичных цифр, которые соответствуют позициям этих цифр в исходном числе:
А10 = an.,x10nl + an.2x10"-2 + ... + а,х10 + а0х10°.
Вычисление данного полинома лучше производить по схеме Горнера:
А10 - (...(0+an.,)x10+an.2)x10+ ... + а,)х10+а0.
Например, число 3405 по этим формулам может быть представлено так:
3405=Зх103+4х102+Ох101+5х100=(((0+3)х10+4)х10+0)х10+5.
Ниже приведена программа преобразования целого десятичного числа в символьном виде из диапазона 0..4 294 967 295 в эквивалентное двоичное представление. Для ввода числа с клавиатуры используем функцию 3fh MS D0S. Она удобна тем, что возвращает количество действительно введенных символов в регистре AL.
:prg06_02.asm - программа преобразования целого десятичного числа в символьном виде :из диапазона 0..4294967295 в эквивалентное двоичное представление.
:Вход: ввод с клавиатуры числа в десятичной системе счисления (не более 10 цифр).
:Выход: двоичное число-результат преобразования в регистре ЕАХ.
add eax.edx
mul ten
jc exit_e
inc si
loop ml m2: mov dl .[si]
anddl.Ofh преобразуем ASCI I->BCD
add еах^х;результат преобразования в регистре ЕАХ
jncexit результат вышел за границы операнда exit_e: .выводим строку string_e на экран
При необходимости вы можете изменить программу, так чтобы в ней использовались регистры меньшей разрядности.
Ввод целых десятичных чисел из диапазона 0..999 999 999 999 999 999
Второй способ преобразования десятичных чисел хотя и выглядит несколько экзотически, вполне работоспособен. Данный способ предполагает использование особенностей некоторых команд сопроцессора. В материале урока 19 «Архитектура и программирование сопроцессора» учебника мы перечисляли форматы данных, которые поддерживает сопроцессор. Перечислим их еще раз:
двоичные целые числа в трех форматах — 16, 32 и 64 бита; упакованные целые десятичные (BCD) числа — максимальная длина -18 упакованных десятичных цифр (9 байт); вещественные числа в трех форматах — коротком (32 бита), длинном (64 бита), расширенном (80 бит).Для нас интерес представляют форматы целых двоичных и упакованных десятичных (BCD) чисел, а также команды обмена этими значениями с вершиной сопроцессора. Процесс преобразования десятичного целого числа, вводимого с клавиатуры, показан в программе ниже. Необходимо отметить, что этот способ преобразования позволяет расширить диапазон значений 0..999 999 999 999 999 999.
:prg06_03.asm - программа ввода целых десятичных чисел из диапазона
:0..999 999 999 999 999 999 и преобразования их в эквивалентное двоичное представление.
;Вход: ввод с клавиатуры числа в десятичной системе счисления
:в диапазоне значений 0..999 999 999 999 999 999.
:Выход: двоичное число-результат преобразования в области памяти stnng_bin.
.data
db 0 :барьер. если введенное количество цифр нечетно string db 20 dup (0) максимальное исходное число состоит из 18 цифр (20 - с учетом
Od0ah)
len_string=$-string adr_string dd string
string_pack dt 0 :сюда упаковывается исходное значение
len_string_pack=$-string_pack adr_string_pack dd string_pack результат - двоичное значение различной разрядности:
string_bin_byte label byte
string_bin_word label word
string_bin_word label word
string_bin_dword label dword
string_bindq 0 ;поле для результата - эквивалентного двоичного представления
--------вводим с экрана----------------............-----
movbx.O стандартный дескриптор - клавиатура
movcx. len_str"ing
lea dx.string :формируем указатель на строку string
movah.3fh :номер функции DOS
int 21h
jc exit ;переход в случае ошибки
;в регистре AL - количество действительно введенных десятичных цифр :преобразуем строку с десятичными числами в ее двоичный эквивалент
mov ex.ax
subcx,2 корректируем счетчик цикла (чтобы не учитывать OdOah, вводимые 3fh)
jeexz exit :число не было введено
Ids si ,adr_string
add si,cx
dec si указатель на последнюю введенную десятичную цифру
les di.adr_string_pack ml: std :флаг df=l - работаем со строкой string, начиная с ее конца
хог ах.ах
lodsb
and al. Of h
shl ax.8
lodsb
shl al .4
add al.ah :в AL две очередные упакованные цифры
eld :флаг df-1 - работаем со строкой string_pack. начиная с ее начала
stosb
sub ex.2
emp ex. 0
J9 ml ;конец преобразования в упакованное представление
fI nit инициализируем сопроцессор ;теперь преобразуем в эквивалентное двоичное представление:
fbld string_pack :помещаем в стек сопроцессора 'fistp string_bin ;и извлекаем эквивалентное двоичное представление в поле string_bin
Приведенная программа преобразует любое значение из диапазона 0..1018-!. Интересно отметить количественное значение максимальной двоичной величины, соответствующее верхней границе диапазона, — это +0de0b6b3a763ffffl6. Запомните его, оно пригодится нам при рассмотрении обратного преобразования Для вывода на консоль — из двоичного в десятичное представление. Извлечь значение нужной разрядности можно, если ввести директивой label соответствующие идентификаторы в исходный текст программы (что и сделано в нашем сегменте кода):
string_bin_byte label byte
string_bin_word label word
string_bin_word label word
string_bin_dword label dword
Ввод целых десятичных чисел из диапазона 0..до бесконечности
Для преобразования десятичного числа произвольной разрядности из символьного представления в двоичное потрудиться придется несколько больше. Основа для этой работы была создана в материале, посвященном арифметическим операциям для чисел произвольной разрядности. Поэтому наши действия при разработке программы преобразования напомнит игру с конструктором, когда из готовых компонент будет создаваться новый продукт.
Исходными компонентами программы преобразования десятичного числа произвольной разрядности из символьного представления в двоичное будут являться макрокоманда умножения N-байтного числа на число размером М байт и программа сложения чисел размером N байт без учета знака. Алгоритм вычисления двоичного эквивалента будет таким же, как рассмотренный выше, — вычисление полинома по схеме Горнера. Ниже приведен вариант реализующей его программы. Расположение байтов результата — по схеме, естественной для микропроцессоров Intel, то есть младший байт располагается по младшему адресу.
:prg06_04.asm - программа ввода целых десятичных чисел из диапазона 0..». ;Вход: ввод с клавиатуры числа в десятичной системе счисления длиной до 20 цифр. ;Выход: двоичное число - результат преобразования в области памяти string_bin.
:см. описание макрокоманд add_unsign_N_l и mul_unsign_NM в главе 1 add_unsIgn_N_lmacro summand_l, summand_2, N
endm mul_unsign_NM macro u.i.v.j.w
endm .data
string db 22 dup (0) максимальное число состоит из 20 цифр (22 - с учетом OdOah) len_string-$-string tendd 10
string_bindb 10 dup (0) максимальная длина двоичного числа - 10 байт 1en_stri ng_bi n-$-stri ng_bi n
carry db 0 :перенос сложения последних байтов
adr_string_bindd string_bin string_bin_w db len_string_bin+l dup (0) результат умножения для макроса
;mul_unsign_NM = len_string_bin+l байт len_string_bin_w =$-string_bin_w adr_string_bin_w dd string_bin_w k db 0 :перенос 0 < k < 255
b dw lOOh ;размер машинного слова
.code
movbx.O стандартный дескриптор - клавиатура
mov cx.len_string
lea dx.string ;формируем указатель на строку string
movah,3fh :номер функции DOS
int 21h
jc exit :переход в случае ошибки :в регистре AL - количество действительно введенных десятичных цифр
mov ecx.eax
sub есх.2 корректируем счетчик цикла (чтобы не учитывать OdOah. вводимые 3fh)
jcxz $+4 :число не было введено
jmp $+5
jmp exit cont_l:dec ecx ;не умножать на 10 последнюю цифру числа
jcxz $+4 юднозначное число
tjmp S+5 jmp m2 lea si.string ;формируем указатель на строку string хог еах.еах :еах:=0 ml: хог edx.edx
mov dl.[si]
and dl.Ofh преобразуем ASCI I->BCD add_unsign_N_lstring_bin.dl. len_string_bin ¦.умножаем на 10
mul_unsign_NM string_bin. len_string_bin.ten,l. string_bin_w :копируем string bin_w в string_bin
eld
push si push ex
Ids si .adr_sthng_bin_w
lesdi.adr_string_bin
mov cx.len_string_bin_w repmovsb pop ex pop si
inc si dec ex jcxm2 ' jmp ml m2: mov dl .[si] and dl.Ofh
¦ add_unsign_N_lstring_bin.dl, len_string_bin
¦ результат преобразования - в строке string_bin
Одно из направлений совершенствования этой программы — динамическое \ выделение памяти для всех чисел с неизвестной длиной. Необходимо заметить, i что способ преобразования длинных чисел универсален — его можно использовать и для преобразования значений, которые укладываются в представимые в микропроцессоре диапазоны типов данных.
Ввод вещественных чисел
Теперь у нас все готово для того, чтобы выполнить ввод с клавиатуры символьного представления вещественного числа и преобразование его в соответствующий двоичный эквивалент. В уроке 19 «Архитектура и программирование сопроцессора» учебника мы обсуждали понятие вещественного числа. Отмечалось, что вещественное число имеет две формы записи — с плавающей точкой (34.89) и научную (3.45е-3=3.45х103). Для преобразования вещественного числа из символьного представления в эквивалентное двоичное можно предложить несколько способов. Самый простой — использовать возможность загрузки в сопроцессор упакованного BCD-числа. В этом случае алгоритм преобразования состоит в следующем. Символьная строка с вещественным числом вводится в память, где она преобразуется в упакованное BCD-число. При вводе указанной символьной строки запоминается положение плавающей точки. Полученное упакованное BCD-число загружается в сопроцессор, после чего оно делится на 10 в степени, соответствующей положению плавающей точки в исходном числе. Для малых чисел (в диапазоне до |1018-1|) этот способ вполне хорош. Его можно расширить, если вводить число в научном формате, при этом процесс перевода мантиссы аналогичен рассмотренному выше, но при подготовке к делению на степень 10 необходимо учесть значение степени, указанное после символа «е». Но все равно, несмотря на расширения диапазона, разряднорть мантиссы ограничена 18 цифрами. Устранить этот недостаток можно, используя операции с числами произвольной разрядности. Этот способ интересен своей универсальностью, поэтому уделим ему основное внимание.
Итак, разработаем программу ввода вещественного числа с клавиатуры в одном из двух возможных форматов — простом формате с плавающей точкой. Доработать программу для использования научного формата для вас не составит труда.
В качестве знаков, разделяющих мантиссу на целую и дробную части, можно использовать как запятую, так и точку. Суть алгоритма преобразования состоит в следующем. Производится ввод с клавиатуры символов вещественного числа. После ввода анализируются символы буфера, куда было помещены символы введенного числа, на предмет выяснения положения плавающей точки. Обнаруженная позиция запоминается. Относительно нее введенные символы делятся на символы цифр целой и дробной частей. Используя алгоритм преобразования символьного представления десятичного числа в двоичный эквивалент, преобразуется целая часть вещественного числа. Дробная часть вещественного числа также преобразуется в двоичный эквивалент. Это преобразование производится с использованием сопроцессора по формуле: ((...(u_m/b+u1_m)/b+...+u_2)/b+u.,)/b, где un — символы десятичных цифр дробной части вещественного числа u_mu1_n,..-u.2u_,, b=10. После того как данное выражение вычислено (его результат находится в вершине стека сопроцессора), производится сложение его результата с преобразованной целой частью вещественного числа. Все, теперь в вершине стека сопроцессора находится вещественное число — эквивалент своего исходного символьного представления. Текст программы преобразования вещественного числа из символьного представления достаточно велик и по этой причине приведен на дискете (prg06_05.asm). Заметьте, что с целью экономии места никаких проверок
на правильность формата вводимого вещественного числа в программе не делается.
Последнее замечание — об ограничениях на размерность исходного числа. Здесь следует различать размерности целой и дробной частей. Что касается дробной части, то здесь вообще ограничений нет, за исключением тех, которые накладывает сам сопроцессор на вводимые в его регистры значения. Для целой части узкое место — максимальная размерность операнда в команде целочисленного сложения FIADD, которая составляет 32 бита.
Ввод шестнадцатеричных и двоичных чисел мы рассматривать не будем, так как общие принципы их ввода аналогичны рассмотренным выше для десятичных чисел. Потребность в вводе с клавиатуры шестнадцатеричных и двоичных чисел возникает значительно реже, чем десятичных.
Вывод чисел на консоль
В этом разделе мы рассмотрим алгоритмы обратного преобразования чисел — из внутреннего двоичного представления в число в символьном виде, формат записи которого соответствует правилам требуемой системы счисления. Необходимо предупредить читателя, что рассмотрение обратного преобразования не будет симметричным рассмотренному выше прямому преобразованию. И в подтверждение этому начнем обсуждение проблемы вывода чисел на консоль с алгоритма преобразования шестнадцатеричных чисел в символьное представление.
Вывод шестнадцатеричных чисел
Умение работать с шестнадцатеричными числами — необходимое условие успешного программирования на низком уровне. Шестнадцатеричные числа по сравнению с двоичными являются более естественными для анализа внутреннего представления информации в компьютере. Вспомним, что каждый байт — это совокупность двух тетрад, а диапазон значений, представимых одной тетрадой, как раз совпадает с диапазоном значений, которые может принимать однозначное шестнадцатеричное число. Поэтому сам процесс преобразование шестнадцатеричных чисел в символьное представление особого труда не представляет. Например, алгоритм вывода на консоль содержимого одного байта состоит в выделении некоторым способом значений его младшей и старшей тетрад и дальнейшее их преобразование в символьное представление. Если нужно вывести на консоль символьное представление более чем одного байта, то процесс выделения тетрад и их преобразования выполняется последовательно необходимое количество раз.
В качестве полезной иллюстрации алгоритма преобразования шестнадцате-ричной информации в символьное представление рассмотрим макрокоманду 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