Учебный курс. Часть 22. Вывод чисел на консоль

Автор: xrnd | Рубрика: Исходники, Учебный курс | 31-07-2010 | Распечатать запись Распечатать запись

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

Следовательно, задача сводится к тому, чтобы преобразовать двоичное число в строку символов, а затем вывести эту строку на экран. Все процедуры в этой части являются лишь примерами, вы можете использовать их или написать свои собственные процедуры, более удобные для вас.

Для начала рассмотрим две полезные процедуры, которые будут использоваться в дальнейшем. Чтобы постоянно не обращаться в коде в функции DOS 09h, удобно написать маленькую процедуру для вывода строки:

;Процедура вывода строки на консоль
; DI - адрес строки
print_str:
    push ax
    mov ah,9                ;Функция DOS 09h - вывод строки
    xchg dx,di              ;Обмен значениями DX и DI
    int 21h                 ;Обращение к функции DOS
    xchg dx,di              ;Обмен значениями DX и DI
    pop ax
    ret

В качестве параметра ей передаётся адрес строки в регистре DI. Строка должна оканчиваться символом ‘$’. Здесь используется команда XCHG, которая выполняет обмен значениями двух операндов.

Вторая полезная процедура — вывод конца строки. Она вызывает первую процедуру для вывода двух символов CR(13) и LF(10). Вызывается без параметров и не изменяет регистры.

;Процедура вывода конца строки (CR+LF)
print_endline:
    push di
    mov di,endline          ;DI = адрес строки с символами CR,LF
    call print_str          ;Вывод строки на консоль
    pop di
    ret
...
endline db 13,10,'$'

Вывод чисел в двоичном виде

Алгоритм вывода в двоичном виде очень прост. Нужно проанализировать все биты числа и поместить в строку символы ‘0’ или ‘1’ в зависимости от значения соответствующего бита. Удобно делать это с помощью циклического сдвига в цикле. Сдвинутый бит оказывается в флаге CF, а после завершения цикла в регистре то же значение, что и в начале. Процедура byte_to_bin_str преобразует байт в регистре AL в строку. Адрес буфера для строки передаётся в регистре DI. Процедура всегда записывает в буфер 8 символов, так как в байте 8 бит.

;Процедура преобразования байта в строку в двоичном виде
; AL - байт.
; DI - буфер для строки (8 символов). Значение регистра не сохраняется.
byte_to_bin_str:
    push cx                 ;Сохранение CX
    mov cx,8                ;Счётчик цикла
 
btbs_lp:
    rol al,1                ;Циклический сдвиг AL влево на 1 бит
    jc btbs_1               ;Если выдвинутый бит = 1, то переход
    mov byte[di],'0'        ;Добавление символа '0' в строку
    jmp btbs_end
btbs_1:
    mov byte[di],'1'        ;Добавление символа '1' в строку
btbs_end:
    inc di                  ;Инкремент DI
    loop btbs_lp            ;Команда цикла
 
    pop cx                  ;Восстановление CX
    ret                     ;Возврат из процедуры

Используя эту процедуру, легко написать ещё одну для вывода слова в двоичном виде:

;Процедура преобразования слова в строку в двоичном виде
; AX - слово
; DI - буфер для строки (16 символов). Значение регистра не сохраняется.
word_to_bin_str:
    xchg ah,al              ;Обмен AH и AL
    call byte_to_bin_str    ;Преобразование старшего байта в строку
    xchg ah,al              ;Обмен AH и AL
    call byte_to_bin_str    ;Преобразование младшего байта в строку
    ret

И наконец вот две процедуры, которые делают то, что нужно 🙂 Буфер имеет размер 17 символов, так как в слове 16 бит + символ ‘$’, обозначающий конец строки.

;Процедура вывода байта на консоль в двоичном виде
; AL - байт
print_byte_bin:
    push di
    mov di,buffer           ;DI = адрес буфера
    call byte_to_bin_str    ;Преобразование байта в AL в строку
    mov byte[di],'$'        ;Добавление символа конца строки
    sub di,8                ;DI = адрес начала строки
    call print_str          ;Вывод строки на консоль
    pop di
    ret
 
;Процедура вывода слова на консоль в двоичном виде
; AX - слово
print_word_bin:
    push di
    mov di,buffer           ;DI = адрес буфера
    call word_to_bin_str    ;Преобразование слова в AX в строку
    mov byte[di],'$'        ;Добавление символа конца строки
    sub di,16               ;DI = адрес начала строки
    call print_str          ;Вывод строки на консоль
    pop di
    ret
...
buffer  rb 17

Полный исходный код примера вы можете скачать отсюда: printbin.asm. Результат работы программы выглядит вот так:

Вывод чисел в шестнадцатеричном виде

Этот пример по структуре похож на предыдущий, поэтому для краткости я рассмотрю только сами процедуры преобразования числа в строку. Преобразование в шестнадцатеричный вид удобно выполнять группами по 4 бита, то есть по тетрадам. Каждая тетрада будет представлять собой одну шестнадцатеричную цифру. Я написал отдельную процедуру для преобразования тетрады в символ цифры:

;Процедура преобразования числа (0-15) в шестнадцатеричную цифру
; вход : AL - число (0-15)
; выход: AL - шестнадцатеричная цифра ('0'-'F')
to_hex_digit:
    add al,'0'              ;Прибавляем символ '0' (код 0x30)
    cmp al,'9'              ;Сравнение с символом '9' (код 0x39)
    jle thd_end             ;Если получилось '0'-'9', то выход
    add al,7                ;Прибавляем ещё 7 для символов 'A'-'F'
thd_end:
    ret

Если значение тетрады от 0 до 9, то достаточно только прибавить код символа ‘0’ (0x30). А если значение больше 9, то надо прибавить ещё 7, чтобы получилась буква ‘A’-‘F’.

Теперь легко можно преобразовать байт в шестнадцатеричную строку, достаточно каждую из его тетрад заменить соответствующей цифрой:

;Процедура преобразования байта в строку в шестнадцатеричном виде
; AL - байт.
; DI - буфер для строки (2 символа). Значение регистра не сохраняется.
byte_to_hex_str:
    push ax
    mov ah,al               ;Сохранение значения AL в AH
    shr al,4                ;Выделение старшей тетрады
    call to_hex_digit       ;Преобразование в шестнадцатеричную цифру
    mov [di],al             ;Добавление символа в строку
    inc di                  ;Инкремент DI
    mov al,ah               ;Восстановление AL
    and al,0Fh              ;Выделение младшей тетрады
    call to_hex_digit       ;Преобразование в шестнадцатеричную цифру
    mov [di],al             ;Добавление символа в строку
    inc di                  ;Инкремент DI
    pop ax
    ret

Преобразование слова также не представляет трудности — сначала преобразуем старший байт, затем младший:

;Процедура преобразования слова в строку в шестнадцатеричном виде
; AX - слово
; DI - буфер для строки (4 символа). Значение регистра не сохраняется.
word_to_hex_str:
    xchg ah,al              ;Обмен AH и AL
    call byte_to_hex_str    ;Преобразование старшего байта в строку
    xchg ah,al              ;Обмен AH и AL
    call byte_to_hex_str    ;Преобразование младшего байта в строку
    ret

Полный исходный код примера: printhex.asm. Результат работы программы выглядит вот так:

Вывод чисел в десятичном виде

С десятичными числами немного сложнее. Для начала займёмся числами без знака. Чтобы преобразовать число в десятичную строку необходимо в цикле делить его на 10 (это основание системы счисления). Остатки от деления дают нам значения десятичных цифр. Первый остаток — младшая цифра, последний — старшая. Деление продолжается пока частное не равно нулю.

Например, если есть число 125. Делим его на десять: получаем 12, 5 в остатке. Потом делим 12 на десять: получаем 1, 2 в остатке. Наконец, 1 делим на 10: получаем 0, 1 в остатке. Цифры числа, начиная с младшей: 5, 2, 1. Так как обычно десятичные числа пишут, начиная со старшей цифры, то необходимо переставить их наоборот 🙂 Я для этого использовал стек.

В первом цикле производится деление, полученные остатки преобразуются в цифры и помещаются в стек. Во втором цикле символы извлекаются из стека (в обратном порядке) и помещаются в строку. Так как максимальное значение слова без знака 65536 (5 цифр), то в буфер записывается максимум 5 символов.

;Процедура преобразования слова в строку в десятичном виде (без знака)
; AX - слово
; DI - буфер для строки (5 символов). Значение регистра не сохраняется.
word_to_udec_str:
    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                  ;Восстановление символа из стека
    mov [di],dl             ;Сохранение символа в буфере
    inc di                  ;Инкремент адреса буфера
    loop wtuds_lp2          ;Команда цикла
 
    pop bx
    pop dx
    pop cx
    pop ax
    ret

Для вывода байта можно преобразовать его в слово и воспользоваться той же процедурой:

;Процедура преобразования байта в строку в десятичном виде (без знака)
; AL - байт.
; DI - буфер для строки (3 символа). Значение регистра не сохраняется.
byte_to_udec_str:
    push ax
    xor ah,ah               ;Преобразование байта в слово (без знака)
    call word_to_udec_str   ;Вызов процедуры для слова без знака
    pop ax
    ret

Теперь разберёмся с числами со знаком. Сначала нужно проверить старший бит числа. Если число положительное, то его можно преобразовать также как число без знака. Если число отрицательное, то добавляем в строку символ ‘-‘, а затем инвертируем число и преобразуем как беззнаковое.

;Процедура преобразования слова в строку в десятичном виде (со знаком)
; AX - слово
; DI - буфер для строки (6 символов). Значение регистра не сохраняется.
word_to_sdec_str:
    push ax
    test ax,ax              ;Проверка знака AX
    jns wtsds_no_sign       ;Если >= 0, преобразуем как беззнаковое
    mov byte[di],'-'        ;Добавление знака в начало строки
    inc di                  ;Инкремент DI
    neg ax                  ;Изменение знака значения AX
wtsds_no_sign:
    call word_to_udec_str   ;Преобразование беззнакового значения
    pop ax
    ret
;Процедура преобразования байта в строку в десятичном виде (со знаком)
; AL - байт.
; DI - буфер для строки (4 символа). Значение регистра не сохраняется.
byte_to_sdec_str:
    push ax
    movsx ax,al             ;Преобразование байта в слово (со знаком)
    call word_to_sdec_str   ;Вызов процедуры для слова со знаком
    pop ax
    ret

Полный исходный код примера: printdec.asm. Результат работы программы выглядит вот так:

Как вывести на консоль в десятичном виде очень большое число (> 32 бит) читайте здесь.

Вывод чисел в восьмеричном виде

Выводить числа в восьмеричном виде приходится достаточно редко, поэтому подробно описывать не буду. Можно либо делить число последовательно на 8, либо преобразовывать в цифры группы по 3 бита. Я использовал второй вариант. Смотрите код примера: printoct.asm. Результат работы программы:

Вывод чисел в других системах счисления

Реализуется также, как вывод в десятичном виде — с помощью алгоритма последовательного деления на основание системы счисления. Например, если вам нужно вывести число в пятеричной системе счисления, делить надо на 5, а не на 10.

Упражнение

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

Ещё раз ссылки на все примеры

  • printbin.asm — вывод чисел на консоль в двоичном виде
  • printoct.asm — вывод чисел на консоль в восьмеричном виде
  • printdec.asm — вывод чисел на консоль в десятичном виде (со знаком и без знака)
  • printhex.asm — вывод чисел на консоль в шестнадцатеричном виде

Следующая часть »

Комментарии: