Как вывести число в ассемблере
Ввод-вывод десятичных чисел
Методические указания к выполнению лабораторной работы
1) ознакомиться с теоретическим материалом;
2) разобрать реализацию приведенных задач;
3) дописать необходимые команды, создать исполняемые файлы и выполнить программы, реализующие решение задач 1, 2.
4) проверить правильность работы программ на тестах.
Для самопроверки и для контроля преподавателем необходимо выполнить все задачи.
Теоретические сведения
Анализ таблицы кодов ASCII показывает следующее.
В системе кодировки ASCII для любой цифры от 0 до 9 справедливо соотношение
Для получения числа 5 необходимо еще вычесть 30 h из полученного кода:
mov 21 h ; в al код символа ‘5’
ЗАДАЧА 1
Пусть в сегменте данных под символическим именем N хранится беззнаковое десятичное число (от 0 до 255). Необходимо записать по адресу KOD цифры (как символы) из десятичной записи числа.
РЕШЕНИЕ
Чтобы получить эти цифры как символы (их затем можно будет вывести на экран), следует к цифре прибавить код ‘0’ (30 h ).
mov KOD +2, ah ; записали последнюю цифру
add ax,’00’ ; ah=b+’0’, al=a+’0’
mov KOD +1, ah ; записали среднюю цифру
ЗАДАЧА 2
Ввести десятичное число и записать его в сегмент данных под символическим именем N (размером в один байт).
РЕШЕНИЕ
По очереди вводим цифры и формируем число по схеме Горнера.
Пусть уже введены первые цифры числа, например, 2 и 1, и по ним сформировано число 21. Пусть следующей введена цифра 3. Тогда умножаем предыдущее число на 10 и прибавляем к нему новую цифру: 21*10+3=213. И так для каждой новой цифры.
int 21 h ; в al следующий символ
je End ; конец ввода
cbw ; расширение до слова
20. Вывод чисел на консоль в ассемблере i8086
Архитектура ЭВМ и систем › Язык Assembler › i8086 › 20. Вывод чисел на консоль в ассемблере i8086
Статья основана на материале xrnd с сайта asmworld (из учебного курса по программированию на ассемблер 16-битного процессора 8086 под DOS).
В качестве примера программирования процедур займёмся такой важной проблемой, как вывод на консоль чисел в различных системах счисления. Проблема эта возникает потому, что в ассемблере нет никаких специальных средств для вывода чисел, а с помощью стандартных функций можно выводить только строки.
Следовательно, задача сводится к тому, чтобы преобразовать двоичное число в строку символов, а затем вывести эту строку на экран. Все процедуры в этой части являются лишь примерами, вы можете использовать их или написать свои собственные процедуры, более удобные для вас.
Для начала рассмотрим две полезные процедуры, которые будут использоваться в дальнейшем. Чтобы постоянно не обращаться в коде в функции DOS 09h, удобно написать маленькую процедуру для вывода строки:
Вторая полезная процедура — вывод конца строки. Она вызывает первую процедуру для вывода двух символов CR(13) и LF(10). Вызывается без параметров и не изменяет регистры.
Вывод чисел в двоичном виде
Алгоритм вывода в двоичном виде очень прост. Нужно проанализировать все биты числа и поместить в строку символы ‘0’ или ‘1’ в зависимости от значения соответствующего бита. Удобно делать это с помощью циклического сдвига в цикле. Сдвинутый бит оказывается в флаге CF, а после завершения цикла в регистре то же значение, что и в начале. Процедура byte_to_bin_str преобразует байт в регистре AL в строку. Адрес буфера для строки передаётся в регистре DI. Процедура всегда записывает в буфер 8 символов, так как в байте 8 бит.
Используя эту процедуру, легко написать ещё одну для вывода слова в двоичном виде:
Результат работы программы выглядит вот так:
Вывод чисел в шестнадцатеричном виде
Этот пример по структуре похож на предыдущий, поэтому для краткости я рассмотрю только сами процедуры преобразования числа в строку. Преобразование в шестнадцатеричный вид удобно выполнять группами по 4 бита, то есть по тетрадам. Каждая тетрада будет представлять собой одну шестнадцатеричную цифру. Я написал отдельную процедуру для преобразования тетрады в символ цифры:
Если значение тетрады от 0 до 9, то достаточно только прибавить код символа ‘0’ (0x30). А если значение больше 9, то надо прибавить ещё 7, чтобы получилась буква ‘A’-‘F’.
Теперь легко можно преобразовать байт в шестнадцатеричную строку, достаточно каждую из его тетрад заменить соответствующей цифрой:
Преобразование слова также не представляет трудности — сначала преобразуем старший байт, затем младший:
Результат работы программы выглядит вот так:
Вывод чисел в десятичном виде
С десятичными числами немного сложнее. Для начала займёмся числами без знака. Чтобы преобразовать число в десятичную строку необходимо в цикле делить его на 10 (это основание системы счисления). Остатки от деления дают нам значения десятичных цифр. Первый остаток — младшая цифра, последний — старшая. Деление продолжается пока частное не равно нулю.
Например, если есть число 125. Делим его на десять: получаем 12, 5 в остатке. Потом делим 12 на десять: получаем 1, 2 в остатке. Наконец, 1 делим на 10: получаем 0, 1 в остатке. Цифры числа, начиная с младшей: 5, 2, 1. Так как обычно десятичные числа пишут, начиная со старшей цифры, то необходимо переставить их наоборот. Я для этого использовал стек.
В первом цикле производится деление, полученные остатки преобразуются в цифры и помещаются в стек. Во втором цикле символы извлекаются из стека (в обратном порядке) и помещаются в строку. Так как максимальное значение слова без знака 65536 (5 цифр), то в буфер записывается максимум 5 символов.
Для вывода байта можно преобразовать его в слово и воспользоваться той же процедурой:
Теперь разберёмся с числами со знаком. Сначала нужно проверить старший бит числа. Если число положительное, то его можно преобразовать также как число без знака. Если число отрицательное, то добавляем в строку символ ‘-‘, а затем инвертируем число и преобразуем как беззнаковое.
Результат работы программы выглядит вот так:
Как вывести на консоль в десятичном виде очень большое число (> 32 бит) читайте здесь.
Вывод чисел в восьмеричном виде
Выводить числа в восьмеричном виде приходится достаточно редко, поэтому подробно описывать не буду. Можно либо делить число последовательно на 8, либо преобразовывать в цифры группы по 3 бита. Я использовал второй вариант.
Результат работы программы:
Вывод чисел в других системах счисления
Реализуется также, как вывод в десятичном виде — с помощью алгоритма последовательного деления на основание системы счисления. Например, если вам нужно вывести число в пятеричной системе счисления, делить надо на 5, а не на 10.
Упражнение
Напишите программу для вывода на консоль массива слов со знаком в десятичном виде (например, через запятую). Для вывода чисел можете воспользоваться моим примером или написать свою собственную процедуру. Результаты можете писать в комментариях или на форуме.
Исходные коды программ целиком:
1. Вывод в двоичном виде:
FasmWorld Программирование на ассемблере FASM для начинающих и не только
Учебный курс. Часть 22. Вывод чисел на консоль
Автор: xrnd | Рубрика: Исходники, Учебный курс | 31-07-2010 |
Распечатать запись
В качестве примера программирования процедур займёмся такой важной проблемой, как вывод на консоль чисел в различных системах счисления. Проблема эта возникает потому, что в ассемблере нет никаких специальных средств для вывода чисел, а с помощью стандартных функций можно выводить только строки.
Следовательно, задача сводится к тому, чтобы преобразовать двоичное число в строку символов, а затем вывести эту строку на экран. Все процедуры в этой части являются лишь примерами, вы можете использовать их или написать свои собственные процедуры, более удобные для вас.
Для начала рассмотрим две полезные процедуры, которые будут использоваться в дальнейшем. Чтобы постоянно не обращаться в коде в функции DOS 09h, удобно написать маленькую процедуру для вывода строки:
В качестве параметра ей передаётся адрес строки в регистре DI. Строка должна оканчиваться символом ‘$’. Здесь используется команда XCHG, которая выполняет обмен значениями двух операндов.
Вторая полезная процедура — вывод конца строки. Она вызывает первую процедуру для вывода двух символов CR(13) и LF(10). Вызывается без параметров и не изменяет регистры.
Вывод чисел в двоичном виде
Алгоритм вывода в двоичном виде очень прост. Нужно проанализировать все биты числа и поместить в строку символы ‘0’ или ‘1’ в зависимости от значения соответствующего бита. Удобно делать это с помощью циклического сдвига в цикле. Сдвинутый бит оказывается в флаге CF, а после завершения цикла в регистре то же значение, что и в начале. Процедура byte_to_bin_str преобразует байт в регистре AL в строку. Адрес буфера для строки передаётся в регистре DI. Процедура всегда записывает в буфер 8 символов, так как в байте 8 бит.
Используя эту процедуру, легко написать ещё одну для вывода слова в двоичном виде:
И наконец вот две процедуры, которые делают то, что нужно 🙂 Буфер имеет размер 17 символов, так как в слове 16 бит + символ ‘$’, обозначающий конец строки.
Полный исходный код примера вы можете скачать отсюда: printbin.asm. Результат работы программы выглядит вот так:
Вывод чисел в шестнадцатеричном виде
Этот пример по структуре похож на предыдущий, поэтому для краткости я рассмотрю только сами процедуры преобразования числа в строку. Преобразование в шестнадцатеричный вид удобно выполнять группами по 4 бита, то есть по тетрадам. Каждая тетрада будет представлять собой одну шестнадцатеричную цифру. Я написал отдельную процедуру для преобразования тетрады в символ цифры:
Если значение тетрады от 0 до 9, то достаточно только прибавить код символа ‘0’ (0x30). А если значение больше 9, то надо прибавить ещё 7, чтобы получилась буква ‘A’-‘F’.
Теперь легко можно преобразовать байт в шестнадцатеричную строку, достаточно каждую из его тетрад заменить соответствующей цифрой:
Преобразование слова также не представляет трудности — сначала преобразуем старший байт, затем младший:
Полный исходный код примера: printhex.asm. Результат работы программы выглядит вот так:
Вывод чисел в десятичном виде
С десятичными числами немного сложнее. Для начала займёмся числами без знака. Чтобы преобразовать число в десятичную строку необходимо в цикле делить его на 10 (это основание системы счисления). Остатки от деления дают нам значения десятичных цифр. Первый остаток — младшая цифра, последний — старшая. Деление продолжается пока частное не равно нулю.
Например, если есть число 125. Делим его на десять: получаем 12, 5 в остатке. Потом делим 12 на десять: получаем 1, 2 в остатке. Наконец, 1 делим на 10: получаем 0, 1 в остатке. Цифры числа, начиная с младшей: 5, 2, 1. Так как обычно десятичные числа пишут, начиная со старшей цифры, то необходимо переставить их наоборот 🙂 Я для этого использовал стек.
В первом цикле производится деление, полученные остатки преобразуются в цифры и помещаются в стек. Во втором цикле символы извлекаются из стека (в обратном порядке) и помещаются в строку. Так как максимальное значение слова без знака 65536 (5 цифр), то в буфер записывается максимум 5 символов.
Для вывода байта можно преобразовать его в слово и воспользоваться той же процедурой:
Теперь разберёмся с числами со знаком. Сначала нужно проверить старший бит числа. Если число положительное, то его можно преобразовать также как число без знака. Если число отрицательное, то добавляем в строку символ ‘-‘, а затем инвертируем число и преобразуем как беззнаковое.
Полный исходный код примера: printdec.asm. Результат работы программы выглядит вот так:
Как вывести на консоль в десятичном виде очень большое число (> 32 бит) читайте здесь.
Вывод чисел в восьмеричном виде
Выводить числа в восьмеричном виде приходится достаточно редко, поэтому подробно описывать не буду. Можно либо делить число последовательно на 8, либо преобразовывать в цифры группы по 3 бита. Я использовал второй вариант. Смотрите код примера: printoct.asm. Результат работы программы:
Вывод чисел в других системах счисления
Реализуется также, как вывод в десятичном виде — с помощью алгоритма последовательного деления на основание системы счисления. Например, если вам нужно вывести число в пятеричной системе счисления, делить надо на 5, а не на 10.
Упражнение
Напишите программу для вывода на консоль массива слов со знаком в десятичном виде (например, через запятую). Для вывода чисел можете воспользоваться моим примером или написать свою собственную процедуру. Результаты можете писать в комментариях.
Ещё раз ссылки на все примеры
Комментарии:
end_str:
call vozvr_kar
pop cx
loop lp_1
mov ah,08h
int 21h
mov ax,4c00h
int 21h
des_vid:
push cx
push dx
xor di,di
xor dx,dx
xor cx,cx
test ax,ax
jns net_mnsa
call minus
neg ax
net_mnsa:
cmp ax,10
jl fsjo
div bx
call dlshe
jmp net_mnsa
dlshe:
add dl,’0′
mov byte[buffer+di],dl
inc di
inc cx
ret
fsjo:
mov dl,al
call dlshe
dec di
vivod_smvl:
mov ah,02h
movzx dx,[buffer+di]
int 21h
dec di
loop vivod_smvl
pop dx
pop cx
ret
zpt:
push ax
push dx
mov ah,02h
mov dl,’,’
int 21h
pop dx
pop ax
ret
minus:
push dx
push ax
mov dl,’-‘
mov ah,02h
int 21h
pop ax
pop dx
ret
vozvr_kar:
push dx
push ax
mov ah,09h
mov dx,vozwr_kar
int 21h
pop ax
pop dx
ret
Вот только не понял, когда в ах число меньше 10-ти, то при его делении выдаётся ошибка и программа закрывается. Так и должно быть?
Извиняюсь, что долго не отвечал.
Сейчас буду разбираться. Ошибка в твоей программе или в моей?
Хорошая программа, но уж слишком запутанная 🙂 Процедуры в перемешку с циклами.
Ошибка в том, что DIV делит DX:AX на BX, а у тебя DX не обнуляется перед делением. Поэтому остаток от предыдущего деления попадает в старшую часть делимого. Аварийное завершение происходит потому что частное не влезает в 16 бит при таком делении.
Попробуй 3-х значные числа выводить, тоже будет косяк. Ты схитрил, взяв 1-2 значные числа и сделав дополнительную проверку )))
В моём примере делится так:
xor dx,dx ;Обнуление старшей части двойного слова div bx ;Деление AX=(DX:AX)/BX, остаток в DX
Доброго времени суток.
В функции преобразования беззнакового целого в строку не вкурил следующего момента:
xor dx,dx ;Обнуление старшей части двойного слова
div bx ;Деление AX=(DX:AX)/BX, остаток в DX
…..
Остаток — в DX, а частное по идее в AX, а на следующем цикле уже получается делимое потерялось и делить будем частное от предыдущего цикла…
На TASMе 2й день пишу, так что не ругайтесь если туплю )
=)))) Дошло…
Извиняюсь, давно на асме(до этого контроллеры немного программировал) не писал,
крыша поехала ))
end_str:
call vozvr_kar
pop cx
loop lp_1
mov ah,08h
int 21h
mov ax,4c00h
int 21h
des_vid:
push cx
push dx
xor di,di
xor cx,cx
test ax,ax
jns net_mnsa
call minus
neg ax
net_mnsa:
cmp ax,0
je decr
xor dx,dx
div bx
add dl,’0′
mov byte[buffer+di],dl
inc di
inc cx
jmp net_mnsa
decr:
dec di
vivod_smvl:
mov ah,02h
movzx dx,[buffer+di]
int 21h
dec di
loop vivod_smvl
pop dx
pop cx
ret
zpt:
push ax
push dx
mov ah,02h
mov dl,’,’
int 21h
pop dx
pop ax
ret
minus:
push dx
push ax
mov dl,’-‘
mov ah,02h
int 21h
pop ax
pop dx
ret
vozvr_kar:
push dx
push ax
mov ah,09h
mov dx,vozwr_kar
int 21h
pop ax
pop dx
ret
Эта программа лучше 🙂 Но она сглючит, если одно из чисел равно 0. Попробуй сам, если не веришь.
Проблема в том, что проверка ax на 0 должна быть после деления и вывода символа.
Да, точно))) исправил. Спасибо.
Я весь массив преобразовал в одну строку, поэтому процедуры всего две — ваша для преобразования(лучше не придумал) и вторая для добавления символа в строку, хотя можно было выделить еще проверку на знак.
use16 ;Генерировать 16-битный код
org 100h ;Программа начинается с адреса 100h
jmp start
array1 dw 2300,-7070,234,890,0,105,-9999,467,9876,-15000,876,31000
arl1 db 12
array2 rb 48;размер вычисляется arl1*4 с запасом(все числа отрицательные и пятизначные + запятые)
start:
xor si,si
mov di,array2
mov ax,array1
movsx cx,[arl1]
pere: mov ax,[array1+si];в ах si-й элемент массива
test ax,ax;проверка на знак
jns pol;если отрицательный
mov dl,2dh ;добавляем «-»
neg ax;и инвертируем
call znak
pol: call pr2_10
cmp cx,1
jz pro ;если последняя цифра «,» не ставим
mov dl,2ch
call znak
inc si
inc si
pro: loop pere
mov dl,24h; конец строки
call znak
mov dx,array2
mov ah,9 ;Функция DOS 09h — вывод строки
int 21h ;Обращение к функции DOS
mov ah,8 ;Ввод символа без эха
int 21h
mov ax,4C00h ;\
int 21h ;/ Завершение программы
znak:
mov [di],dl ;Добавление элемента в строку
inc di ;Инкремент DI
ret
pr2_10:
push ax
push cx
push dx
push bx
xor cx,cx ;Обнуление CX
mov bx,10 ;В BX делитель (10 для десятичной системы)
wtuds_lp1: ;Цикл получения остатков от деления
xor dx,dx ;Обнуление старшей части двойного слова
div bx ;Деление AX=(DX:AX)/BX, остаток в DX
add dl,’0′ ;Преобразование остатка в код символа
push dx ;Сохранение в стеке
inc cx ;Увеличение счетчика символов
test ax,ax ;Проверка AX
jnz wtuds_lp1 ;Переход к началу цикла, если частное не 0.
wtuds_lp2: ;Цикл извлечения символов из стека
pop dx ;Восстановление символа из стека
call znak
loop wtuds_lp2 ;Команда цикла
pop bx
pop dx
pop cx
pop ax
Написано всё правильно и работает хорошо.
В самом начале такой код:
mov ax,array1 movsx cx,[arl1]
Первая команда, как я понял, лишняя.
Вместо второй логично использовать MOVZX или длина массива может быть отрицательной? 🙂
Ну и размер array2 подходит только для твоих чисел.
Число может быть максимум 5-значным + знак + запятая или символ конца строки.
Получается arl1 * 7, тогда это будет «с запасом».
Первые замечания в точку, а с размерами массивов не согласен, так как в первом 12 слов, а во втором 48 байт, поэтому 7/2=3,5.:)
Тогда должна быть директива rw, а не rb.
Попробуй вывести такой массив:
array1 dw 12 dup(8000h)
ASCII-символы занимают байт, зачем использовать директиву rw?
В задании — «массив слов со знаком», поэтому числа меньше 8000h.
mov ah,08h
int 21h
mov ax,4C00h
int 21h
;——————-Данные————————
bufer rb 5
znac rw 1
masiv dw 19,-210,25,10,-156
Razmer dw 5
;————————Описание процедуры ————————-
Prozidyra:
;————————- очищаем используемые регистры———
xor cx,cx
xor di, di
xor si, si
xor ax, ax
До конца не дочитал, но…
Переменная Znac у тебя слово а должна быть байтом (ведь ты туда помещаешь символ знака, а его код — байт). И ещё:
…
mov [znac],’+’
test dh, 10000000b
jz metca1
neg dl ; \ по заданию же массив слов,
xor dh,dh ; / значит просто neg dx
mov [znac],’-’
…
А вообще, мне кажется, надо лучше откоменировать ( например зачем mov cl,dl и т.д.)
neg dl ; \ по заданию же массив слов,
xor dh,dh ; / значит просто neg dx
Чаще мне надо ассемблер вспоминать, тогда бы догадался про Znac.
Слово это 16 бит, т.е. в 8 битном регистре оно поместится не полностью, точнее, вообще не поместится.
«…переводить числа в 10 систему исчисления вычитанием.» — а я понять не мог, что за нестандартное решение.
> Поэтому я пришёл к выводу используется только младая часть регистра dl
> dh содержет числовой бит.
Схитрил ты 🙂 Выбрал маленькие числа для примера. Числовой бит будет только в старшем бите DH.
mov ax,4C00h
int 21h
masiv dw 19,-220,25,10,-156
bxz rw 1 ; — делитель
axz rw 1 ; — Значения ax
dxa rw 1 ; — адрес выводимой строки
cxz rw 1 ;- счётчик
divz rw 1 ;- делимое
Ds8 rb 10 ; буфер 10байт
——————————— процедуры———————
xor_xor: ; очищает все регистры
xor ax,ax
xor bx,bx
xor dx,dx
xor cx,cx
xor si,si
xor di,di
ret
;Процедура вывода строки на консоль
;axz — значение регистра аx
;dxa — содержит адрес выводимой строки
print_str:
mov ah,byte[axz]
mov dx,[dxa]
int 21h
mov ah,08h
int 21h
ret
Ты хорошо поупражнялся!
Без поллитра не разобраться 🙂
Но я заметил некоторые недочёты в твоей программе.
Зачем тебе xor_xor? В нормальной программе это только ухудшит производительность. Вообще ты несколько раз обнуляешь регистры там, где это не обязательно. Можно просто записывать новое значение вместо старого.
Зачем «счетчик» хранится в переменной cxz? Лучше проверять частное на равенство нулю. У тебя счетчик равен 8, значит может быть 8-значное десятичное число. На самом деле слово со знаком — это максимум 5-значное десятичное число.
Короче, программа работает правильно, но её можно довольно сильно упростить и сократить 🙂
В том то и дело, что объявляются просто байты или слова.
Можно интерпретировать отрицательное число со знаком как большое положительное без знака или наоборот.
Нужно внимательно выбирать команды умножения, деления и условных переходов 🙂
Ассемблер. Числа
Обычно числовые данные в Ассемблере представлены в двоичной системе. Арифметические инструкции работают с двоичными данными. Когда числа отображаются на экране или вводятся с клавиатуры, они имеют ASCII-форму.
Представление чисел в Ассемблере
До сих пор мы конвертировали входные данные из ASCII-формы в двоичную систему для выполнения арифметических операций, а затем результат обратно в ASCII-форму. Например:
Результат выполнения программы:
Программирование на ассемблере позволяет более эффективно обрабатывать числа в двоичном виде. Десятичные числа могут быть представлены в следующих двух формах:
ASCII-форма;
BCD-форма (или «Двоично-десятичный код»).
ASCII-представление
В ASCII-представлении десятичные числа хранятся в виде строки ASCII-символов. Есть 4 инструкции для обработки чисел в ASCII-представлении:
AAA (англ. «Adjust After Addition») — настроить после добавления;
AAS (англ. «Adjust After Subtraction») — настроить после вычитания;
AAM (англ. «Adjust After Multiplication») — настроить после умножения;
AAD (англ. «Adjust After Division») — настроить после деления.
В следующем примере мы будем использовать инструкцию AAS:
Результат выполнения программы:
BCD-представление
Существует два типа BCD-представления:
В распакованном BCD-представлении каждый байт хранит двоичный эквивалент каждой десятичной цифры числа. Четыре инструкции настройки ASCII: AAA, AAS, AAM и AAD; также могут использоваться с распакованным BCD-представлением.
В упакованном BCD-представлении каждая цифра сохраняется с использованием 4 бит. Две десятичные цифры упаковываются в 1 байт. Есть две инструкции для обработки этих чисел:
DAA (англ. «Decimal Adjust After Addition») — десятичная настройка после добавления;
DAS (англ. «Decimal Adjust After Subtraction») — десятичная настройка после вычитания.
Обратите внимание, что в упакованном BCD-представлении отсутствует поддержка операций умножения и деления.
В следующей программе мы выполним операцию сложения двух 5-значных десятичных чисел и выведем на экран их сумму: