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

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

В прошлой части мы научились преобразовывать числа в строку и выводить на консоль. А в этой займёмся обратной задачей — вводом чисел с консоли и преобразованием строки в число. Поскольку ввод в двоичном и восьмеричном виде используется редко, я рассмотрю только примеры ввода чисел в десятичном виде (со знаком и без знака) и в шестнадцатеричном.

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

Ввод строки с консоли

Для ввода строки можно использовать функцию MS-DOS 0Ah. Функция позволяет ввести строку длиной от 1 до 254 символов. При вызове в DX передаётся адрес буфера, первый байт которого должен содержать максимально допустимую длину строки. Длина считается вместе с символом конца строки CR (0dh). В результате работы функции во второй байт буфера записывается фактическая длина введённой строки (не считая символа CR). Начиная с третьего байта в буфер записываются символы строки. Подробнее о работе функции можно узнать в раритетном справочнике по DOS 🙂

Чтобы удобнее было использовать эту функцию, можно написать небольшую процедуру. Например, такую:

;Процедура ввода строки c консоли
;  вход: AL - максимальная длина (с символом CR) (1-254)
; выход: AL - длина введённой строки (не считая символа CR)
;        DX - адрес строки, заканчивающейся символом CR(0Dh)
input_str:
    push cx                 ;Сохранение СX
    mov cx,ax               ;Сохранение AX в CX
    mov ah,0Ah              ;Функция DOS 0Ah - ввод строки в буфер
    mov [buffer],al         ;Запись максимальной длины в первый байт буфера
    mov byte[buffer+1],0    ;Обнуление второго байта (фактической длины)
    mov dx,buffer           ;DX = aдрес буфера
    int 21h                 ;Обращение к функции DOS
    mov al,[buffer+1]       ;AL = длина введённой строки
    add dx,2                ;DX = адрес строки
    mov ah,ch               ;Восстановление AH
    pop cx                  ;Восстановление CX
    ret
...
buffer   rb 256

Процедура использует отдельно объявленный буфер. В качестве единственного параметра ей передаётся максимальная длина строки в регистре AL. После возврата из процедуры в этот регистр записывается фактическая длина строки, а в регистр DX — адрес начала строки. Старшая часть AX сохраняется.

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

Для преобразования числа в строку используется так называемая схема Горнера. Любое число в десятичной системе можно представить в следующем виде:

34710 = 3·102 + 4·101 + 7·100 = (3·10 + 4)·10 + 7

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

Следующая процедура преобразует строку в слово в регистре AX. Адрес строки передаётся в DX, длина строки передаётся в AL. Если строка не корректна, процедура возвращает 0 и устанавливает флаг CF. Ошибка возвращается в следующих случаях:

  • строка имеет нулевую длину, то есть пустая строка;
  • строка содержит любые символы кроме десятичных цифр;
  • число находится вне границ диапазона представления чисел (для слова без знака 0…65535).
;Процедура преобразования десятичной строки в слово без знака
;  вход: AL - длина строки
;        DX - адрес строки, заканчивающейся символом CR(0Dh)
; выход: AX - слово (в случае ошибки AX = 0)
;        CF = 1 - ошибка
str_to_udec_word:
    push cx                 ;Сохранение всех используемых регистров
    push dx
    push bx
    push si
    push di
 
    mov si,dx               ;SI = адрес строки
    mov di,10               ;DI = множитель 10 (основание системы счисления)
    movzx cx,al             ;CX = счётчик цикла = длина строки
    jcxz studw_error        ;Если длина = 0, возвращаем ошибку
    xor ax,ax               ;AX = 0
    xor bx,bx               ;BX = 0
 
studw_lp:
    mov bl,[si]             ;Загрузка в BL очередного символа строки
    inc si                  ;Инкремент адреса
    cmp bl,'0'              ;Если код символа меньше кода '0'
    jl studw_error          ; возвращаем ошибку
    cmp bl,'9'              ;Если код символа больше кода '9'
    jg studw_error          ; возвращаем ошибку
    sub bl,'0'              ;Преобразование символа-цифры в число
    mul di                  ;AX = AX * 10
    jc studw_error          ;Если результат больше 16 бит - ошибка
    add ax,bx               ;Прибавляем цифру
    jc studw_error          ;Если переполнение - ошибка
    loop studw_lp           ;Команда цикла
    jmp studw_exit          ;Успешное завершение (здесь всегда CF = 0)
 
studw_error:
    xor ax,ax               ;AX = 0
    stc                     ;CF = 1 (Возвращаем ошибку)
 
studw_exit:
    pop di                  ;Восстановление регистров
    pop si
    pop bx
    pop dx
    pop cx
    ret

Для установки флага CF используется команда STC. Сбросить флаг CF можно командой CLC. В коде данной процедуры она не используется, так как в случае успешного завершения цикла флаг CF всегда будет равен 0.

На основе этой процедуры несложно написать ещё одну для ввода чисел размером 1 байт. Сначала строка преобразуется в слово без знака, а затем выполняется проверка старшей части на равенство нулю. Обратите внимание, что команда TEST всегда сбрасывает флаг CF.

;Процедура преобразования десятичной строки в байт без знака
;  вход: AL - длина строки
;        DX - адрес строки, заканчивающейся символом CR(0Dh)
; выход: AL - байт (в случае ошибки AL = 0)
;        CF = 1 - ошибка
str_to_udec_byte:
    push dx                 ;Сохранение регистров
    push ax
    call str_to_udec_word   ;Преобразование строки в слово (без знака)
    jc studb_exit           ;Если ошибка, то возвращаем ошибку
    test ah,ah              ;Проверка старшего байта AX
    jz studb_exit           ;Если 0, то выход из процедуры (здесь всегда CF = 0)
    xor al,al               ;AL = 0
    stc                     ;CF = 1 (Возвращаем ошибку)
studb_exit:
    pop dx
    mov ah,dh               ;Восстановление только старшей части AX
    pop dx
    ret

Следующие две процедуры совмещают ввод строки с преобразованием строки в число. Для слова нужно ввести максимум 5 символов, а для байта — максимум 3.

;Процедура ввода слова с консоли в десятичном виде (без знака)
; выход: AX - слово (в случае ошибки AX = 0)
;        CF = 1 - ошибка
input_udec_word:
    push dx                 ;Сохранение DX
    mov al,6                ;Ввод максимум 5 символов (65535) + конец строки
    call input_str          ;Вызов процедуры ввода строки
    call str_to_udec_word   ;Преобразование строки в слово (без знака)
    pop dx                  ;Восстановление DX
    ret
;Процедура ввода байта с консоли в десятичном виде (без знака)
; выход: AL - байт (в случае ошибки AL = 0)
;        CF = 1 - ошибка
input_udec_byte:
    push dx                 ;Сохранение DX
    mov al,4                ;Ввод максимум 3 символов (255) + конец строки
    call input_str          ;Вызов процедуры ввода строки
    call str_to_udec_byte   ;Преобразование строки в байт (без знака)
    pop dx                  ;Восстановление DX
    ret

Ввод десятичных чисел со знаком

Ввод чисел со знаком немного труднее. Необходимо проверить первый символ строки: если это символ ‘-‘, то число отрицательное. Кроме того, нужно внимательно проверить диапазон представления (для слова со знаком -32768…32767). Строку без символа ‘-‘ можно преобразовать процедурой для беззнакового числа.

;Процедура преобразования десятичной строки в слово со знаком
;  вход: AL - длина строки
;        DX - адрес строки, заканчивающейся символом CR(0Dh)
; выход: AX - слово (в случае ошибки AX = 0)
;        CF = 1 - ошибка
str_to_sdec_word:
    push bx                 ;Сохранение регистров
    push dx
 
    test al,al              ;Проверка длины строки
    jz stsdw_error          ;Если равно 0, возвращаем ошибку
    mov bx,dx               ;BX = адрес строки
    mov bl,[bx]             ;BL = первый символ строки
    cmp bl,'-'              ;Сравнение первого символа с '-'
    jne stsdw_no_sign       ;Если не равно, то преобразуем как число без знака
    inc dx                  ;Инкремент адреса строки
    dec al                  ;Декремент длины строки
stsdw_no_sign:
    call str_to_udec_word   ;Преобразуем строку в слово без знака
    jc stsdw_exit           ;Если ошибка, то возвращаем ошибку
    cmp bl,'-'              ;Снова проверяем знак
    jne stsdw_plus          ;Если первый символ не '-', то число положительное
    cmp ax,32768            ;Модуль отрицательного числа должен быть не больше 32768
    ja stsdw_error          ;Если больше (без знака), возвращаем ошибку
    neg ax                  ;Инвертируем число
    jmp stsdw_ok            ;Переход к нормальному завершению процедуры
stsdw_plus:
    cmp ax,32767            ;Положительное число должно быть не больше 32767
    ja stsdw_error          ;Если больше (без знака), возвращаем ошибку
 
stsdw_ok:
    clc                     ;CF = 0
    jmp stsdw_exit          ;Переход к выходу из процедуры
stsdw_error:
    xor ax,ax               ;AX = 0
    stc                     ;CF = 1 (Возвращаем ошибку
stsdw_exit:
    pop dx                  ;Восстановление регистров
    pop bx
    ret

Обратите внимание, что для перехода после проверки диапазона используется команда JA (как для сравнения чисел без знака). Ввод байта со знаком реализуется с помощью той же процедуры и дополнительной проверки диапазона значения.

;Процедура преобразования десятичной строки в байт со знаком
;  вход: AL - длина строки
;        DX - адрес строки, заканчивающейся символом CR(0Dh)
; выход: AL - байт (в случае ошибки AL = 0)
;        CF = 1 - ошибка
str_to_sdec_byte:
    push dx                 ;Сохранение регистров
    push ax
    call str_to_sdec_word   ;Преобразование строки в слово (со знаком)
    jc stsdb_exit           ;Если ошибка, то возвращаем ошибку
    cmp ax,127              ;Сравнение результата с 127
    jg stsdb_error          ;Если больше - ошибка
    cmp ax,-128             ;Сравнение результата с -128
    jl stsdb_error          ;Если меньше - ошибка
    clc                     ;CF = 0
    jmp studb_exit          ;Переход к выходу из процедуры
stsdb_error:
    xor al,al               ;AL = 0
    stc                     ;CF = 1 (Возвращаем ошибку)
stsdb_exit:
    pop dx
    mov ah,dh               ;Восстановление только старшей части AX
    pop dx
    ret

Наконец, ещё две процедуры совмещают ввод строки с преобразованием её в слово и байт со знаком.

;Процедура ввода слова с консоли в десятичном виде (со знаком)
; выход: AX - слово (в случае ошибки AX = 0)
;        CF = 1 - ошибка
input_sdec_word:
    push dx                 ;Сохранение DX
    mov al,7                ;Ввод максимум 7 символов (-32768) + конец строки
    call input_str          ;Вызов процедуры ввода строки
    call str_to_sdec_word   ;Преобразование строки в слово (со знаком)
    pop dx                  ;Восстановление DX
    ret
;Процедура ввода байта с консоли в десятичном виде (со знаком)
; выход: AL - байт (в случае ошибки AL = 0)
;        CF = 1 - ошибка
input_sdec_byte:
    push dx                 ;Сохранение DX
    mov al,5                ;Ввод максимум 3 символов (-128) + конец строки
    call input_str          ;Вызов процедуры ввода строки
    call str_to_sdec_byte   ;Преобразование строки в байт (со знаком)
    pop dx                  ;Восстановление DX
    ret

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

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

Преобразование шестнадцатеричной строки в число несколько проще. Удобно реализовать в виде отдельной процедуры преобразование одной цифры. Процедура воспринимает символы ‘A’-‘F’ независимо от регистра. Так как перед вычитанием выполняются проверки, флаг CF всегда будет равен нулю после успешного преобразования.

;Процедура преобразования шестнадцатеричной цифры в число
;  вход: DL - символ-цифра
; выход: DL - значение цифры (0-15, в случае ошибки DL = 0)
;        CF = 1 - ошибка
convert_hex_digit:
    cmp dl,'0'              ;Сравнение с символом '0'
    jl chd_error            ;Если меньше, возвращаем ошибку
    cmp dl,'9'              ;Сравнение с символом '9'
    jg chd_a_f              ;Если больше, то возможно это буква a-f или A-F
    sub dl,'0'              ;Преобразование цифры в число
    ret                     ;Возврат из процедуры (здесь всегда CF = 0)
 
chd_a_f:
    and dl,11011111b        ;Преобразование буквы в верхний регистр
    cmp dl,'A'              ;Сравнение с символом 'A'
    jl chd_error            ;Если меньше, возвращаем ошибку
    cmp dl,'F'              ;Сравнение с символом 'F'
    jg chd_error            ;Если больше, возвращаем ошибку
    sub dl,'A'-10           ;Преобразуем букву в число
    ret                     ;Возврат из процедуры (здесь тоже всегда CF = 0)
 
chd_error:
    xor dl,dl               ;DL = 0
    stc                     ;CF = 1
    ret                     ;Возврат из процедуры

Теперь легко можно написать преобразование шестнадцатеричной строки в слово. Вместо умножения на 16 в процедуре используется сдвиг на 4 бита влево, а вместо сложения — операция ИЛИ. Проверки диапазона значения не нужны, достаточно проверить длину строки и преобразовать цифры.

;Процедура преобразования шестнадцатеричной строки в слово
;  вход: AL - длина строки
;        DX - адрес строки, заканчивающейся символом CR(0Dh)
; выход: AX - слово (в случае ошибки AX = 0)
;        CF = 1 - ошибка
str_to_hex_word:
    push cx                 ;Сохранение регистров
    push dx
    push si
 
    movzx cx,al             ;CX = счётчик цикла = длина строки
    jcxz sthw_error         ;Если длина строки = 0, возвращаем ошибку
    cmp cx,4
    jg sthw_error           ;Если длина строки больше 4, возвращаем ошибку
    xor ax,ax               ;AX = 0
    mov si,dx               ;SI = адрес строки
 
sthw_lp:
    mov dl,[si]             ;Загрузка в DL очередного символа строки
    inc si                  ;Инкремент адреса строки
    call convert_hex_digit  ;Преобразование шестнадцатеричной цифры в число
    jc sthw_error           ;Если ошибка, то возвращаем ошибку
    shl ax,4                ;Сдвиг AX на 4 бита влево
    or al,dl                ;Добавление преобразованной цифры
    loop sthw_lp            ;Команда цикла
    jmp sthw_exit           ;CF = 0
 
sthw_error:
    xor ax,ax               ;AX = 0
    stc                     ;CF = 1
 
sthw_exit:
    pop si                  ;Восстановление регистров
    pop dx
    pop cx
    ret

Для ввода байта используется та же процедура, но дополнительно проверяется длина строки — она должна быть не больше 2.

;Процедура преобразования шестнадцатеричной строки в байт
;  вход: AL - длина строки
;        DX - адрес строки, заканчивающейся символом CR(0Dh)
; выход: AL - байт (в случае ошибки AL = 0)
;        CF = 1 - ошибка
str_to_hex_byte:
    push cx                 ;Сохранение CX
    mov cx,ax               ;Сохранение AX в CX
    cmp al,2                ;Проверка длины строки
    jg sthb_error           ;Если больше 2, возвращаем ошибку
    call str_to_hex_word    ;Преобразование строки в слово
    jnc sthb_exit           ;Если нет ошибки, то переход к выходу из процедуры
sthb_error:
    stc                     ;CF = 1
sthb_exit:
    mov ah,ch               ;Восстановление AH
    pop cx                  ;Восстановление CX
    ret

Ещё две процедуры для ввода и преобразования строки, также как для десятичного ввода:

;Процедура ввода слова с консоли в шестнадцатеричном виде
; выход: AX - слово (в случае ошибки AX = 0)
;        CF = 1 - ошибка
input_hex_word:
    push dx                 ;Сохранение DX
    mov al,5                ;Ввод максимум 4 символов (FFFF) + конец строки
    call input_str          ;Вызов процедуры ввода строки
    call str_to_hex_word    ;Преобразование строки в слово
    pop dx                  ;Восстановление DX
    ret
;Процедура ввода байта с консоли в шестнадцатеричном виде
; выход: AL - байт (в случае ошибки AL = 0)
;        CF = 1 - ошибка
input_hex_byte:
    push dx                 ;Сохранение DX
    mov al,3                ;Ввод максимум 2 символов (FF) + конец строки
    call input_str          ;Вызов процедуры ввода строки
    call str_to_hex_byte    ;Преобразование строки в байт
    pop dx                  ;Восстановление DX
    ret

Полный исходный код примера: inputhex.asm. Как и в примере с десятичными числами, программа повторяет запрос ввода, пока не будут введены корректные данные:

Упражнение

Напишите программу для ввода байта с консоли в двоичном виде. Желательно с проверкой корректности ввода 🙂 Результаты можете писать в комментариях.

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

  • inputdec.asm — ввод десятичных чисел с консоли (со знаком и без)
  • inputhex.asm — ввод шестнадцатеричных чисел с консоли

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

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