Язык ассемблера - Обработка чисел

Обработка чисел в языке ассемблера включает сочетание преобразования систем счисления, преобразования ASCII и арифметических операций. В этой главе подробно объясняется, как вводить и выводить числа в различных системах счисления в программе.


Представление чисел

Внутри компьютера все числа хранятся в двоичном виде, но в программе они могут быть представлены в различных системах счисления:

Система счисленияОснованиеСинтаксис NASMДиапазон чисел (32-битный)
Binary20b10101010или10101010b0 и 1
Восьмеричная80o52или52o0-7
Decimal10420-9
Шестнадцатеричная160x2Aили2Ah0-9, A-F

Связь между ASCII и числами

Всё, что вводится и выводится на экране, — этоASCII-символы, а не двоичные числовые значения.

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

СимволЗначение ASCIIСоответствующее числовое значение
'0'0x30(48)0
'1'0x31(49)1
'9'0x39(57)9
'A'0x41(65)10 (в шестнадцатеричной системе)
'F'0x46(70)15 (в шестнадцатеричной системе)
'a'0x61(97)10 (в шестнадцатеричной системе)

Преобразование символа в число:数值 = ASCII码 - '0'(i.e.ASCII码 - 0x30)

Преобразование числа в символ:ASCII码 = 数值 + '0'(i.e.数值 + 0x30)


Ввод чисел (из строки в числовое значение)

То, что вводит пользователь, — это последовательность символов, которую перед использованием в вычислениях необходимо преобразовать в двоичное числовое значение:

Пример

; Путь к файлу: string_to_int.asm
; Преобразование строки десятичных цифр в целое число

section .data
    input_str db '12345', 0      ; Входная строка "12345"
    input_len dd 5               ; Длина строки

section .bss
    result_val resd 1            ; Здесь хранится преобразованное числовое значение

section .text
    global _start

_start:
    ; Алгоритм преобразования: result = 0
    ; result = result * 10 + (текущий символ - '0')
    mov esi, input_str           ; esi указывает на входную строку
    mov ecx, [input_len]         ; ecx = счётчик цикла
    mov eax, 0                   ; eax = накопленный результат

convert_loop:
    mov ebx, 10
    mul ebx                      ; eax = eax * 10
    ; mul ebx: edx:eax = eax * ebx

    mov bl, [esi]                ; Прочитать текущий символ
    sub bl, '0'                  ; Преобразовать в числовой вид: символ - '0'
    movzx ebx, bl                ; Расширить ebx нулями (bl -> ebx)
    add eax, ebx                 ; eax = eax + значение текущей цифры

    inc esi                      ; Перейти к следующему символу
    loop convert_loop
    ; eax теперь = 12345

    mov [result_val], eax        ; Сохранить результат

    mov eax, 1
    mov ebx, 0
    int 0x80

Анализ алгоритма: рассмотрим "12345" в качестве примера:
Раунд 1: result = 0×10 + 1 = 1
Раунд 2: result = 1×10 + 2 = 12
Раунд 3: result = 12×10 + 3 = 123
...Наконец, мы получаем 12345.


Вывод чисел (числовое значение в строку)

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

Пример

; Путь к файлу: int_to_string.asm
; Преобразовать целочисленное значение в десятичную строку и вывести

section .data
    number dd 12345              ; Число для вывода
    newline db 0xA

section .bss
    output_buf resb 12           ; Выходной буфер (максимальное 32-битное целое — 10 цифр + знак + null)

section .text
    global _start

_start:
    mov eax, [number]            ; Загрузить число
    mov edi, output_buf + 11     ; edi указывает на конец буфера
    mov byte [edi], 0            ; нулевой терминатор (для удобства отладки)
    dec edi

    mov ebx, 10                  ; Делитель = 10
    mov ecx, 0                   ; Счётчик цифр

convert_digit:
    mov edx, 0                   ; Очистить edx (div требует edx:eax)
    div ebx                      ; eax = eax/10, edx = eax%10
    add dl, '0'                  ; Преобразовать остаток в символ
    mov [edi], dl                ; Сохранить в буфер (с конца в начало)
    dec edi                      ; Переместить указатель буфера вперёд
    inc ecx                      ; Счётчик цифр +1
    cmp eax, 0                   ; Частное равно 0?
    jne convert_digit            ; Нет, продолжить цикл

    ; Теперь edi+1 указывает на первый допустимый символ цифры
    inc edi                      ; Исправить указатель, чтобы он указывал на начало строки

    ; Вычислить длину допустимых символов
    ; output_buf + 11 - edi — это допустимая длина

    ; Вывести число
    mov eax, 4
    mov ebx, 1
    mov ecx, edi                 ; Указать на преобразованную строку
    ; Вычислить длину
    mov edx, output_buf + 11
    sub edx, edi                 ; edx = конец буфера - начало строки
    int 0x80

    ; Вывод новой строки
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 1
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

Результат выполнения:

$ nasm -f elf32 int_to_string.asm -o int_to_string.o
$ ld -m elf_i386 int_to_string.o -o int_to_string
$ ./int_to_string
12345

Шестнадцатеричный ввод и вывод

Работа с шестнадцатеричными числами требует обработки как 0-9, так и преобразования A-F/a-f:

Пример

; Путь к файлу: hex_output.asm
; Вывести значение в шестнадцатеричном формате

section .data
    number dd 0x1A2B3C4D         ; Значение для вывода
    hex_prefix db '0x'
    hex_prefix_len equ $ - hex_prefix
    newline db 0xA

section .bss
    hex_buf resb 10              ; 8 шестнадцатеричных цифр + префикс + null

section .text
    global _start

_start:
    ; Вывести префикс "0x"
    mov eax, 4
    mov ebx, 1
    mov ecx, hex_prefix
    mov edx, hex_prefix_len
    int 0x80

    mov eax, [number]            ; Значение для преобразования
    mov edi, hex_buf + 8         ; Конец буфера (8 шестнадцатеричных цифр)
    mov ecx, 8                   ; Цикл 8 раз (32 бита = 8 шестнадцатеричных цифр)

hex_loop:
    mov edx, 0                   ; Подготовка к делению
    mov ebx, 16                  ; Деление на 16
    div ebx                      ; Теперь: eax делится на ebx
    ; Примечание: div ebx здесь на самом деле edx:eax / ebx
    ; Остаток в edx (0-15), частное в eax
    mov ebx, eax                 ; Сохранить частное

    ; Преобразовать остаток в шестнадцатеричный символ
    mov al, dl
    cmp al, 10
    jl  digit_09                 ; 0-9
    add al, 'A' - 10             ; A-F
    jmp store_char

digit_09:
    add al, '0'                  ; 0-9

store_char:
    dec edi
    mov [edi], al
    mov eax, ebx                 ; Восстановить частное
    loop hex_loop

    ; Убран цикл, напрямую используется метод декремента

    ; Вывод шестнадцатеричных цифр (8 цифр)
    mov eax, 4
    mov ebx, 1
    mov ecx, hex_buf
    mov edx, 8
    int 0x80

    ; Вывод новой строки
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 1
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

Полный пример: простой калькулятор сложения

Введите два однозначных числа с клавиатуры, вычислите их сумму и выведите:

Пример

; Путь к файлу: simple_calc.asm
; Ввод двух однозначных чисел (0-9), вычисление суммы и вывод

section .data
    prompt1 db 'Введите первое число (0-9): '
    prompt1_len equ $ - prompt1
    prompt2 db 'Введите второе число (0-9): '
    prompt2_len equ $ - prompt2
    result_msg db 'Сумма = '
    result_msg_len equ $ - result_msg
    newline db 0xA

section .bss
    num1 resb 2                  ; Ввод первого числа (1 цифра + перевод строки)
    num2 resb 2                  ; Ввод второго числа
    result_str resb 3            ; Строка результата (до 2 цифр + перевод строки)

section .text
    global _start

_start:
    ; Приглашение для ввода первого числа
    mov eax, 4
    mov ebx, 1
    mov ecx, prompt1
    mov edx, prompt1_len
    int 0x80

    ; Прочитать первое число
    mov eax, 3
    mov ebx, 0
    mov ecx, num1
    mov edx, 2
    int 0x80

    ; Запрос на ввод второго числа
    mov eax, 4
    mov ebx, 1
    mov ecx, prompt2
    mov edx, prompt2_len
    int 0x80

    ; Прочитать второе число
    mov eax, 3
    mov ebx, 0
    mov ecx, num2
    mov edx, 2
    int 0x80

    ; Вычислить сумму
    mov al, [num1]               ; Прочитать первое число (символ ASCII)
    sub al, '0'                  ; Преобразовать в числовое значение
    mov bl, [num2]               ; Прочитать второе число
    sub bl, '0'                  ; Преобразовать в числовое значение
    add al, bl                   ; Сумма

    ; Преобразовать результат в строку
    mov ah, 0                    ; Подготовка к делению
    mov bl, 10                   ; Разделить на 10
    div bl                       ; al = цифра десятков, ah = цифра единиц

    ; Сохранить результат
    add al, '0'                  ; Преобразовать цифру десятков в символ
    mov [result_str], al         ; Сохранить цифру десятков
    add ah, '0'                  ; Преобразовать цифру единиц в символ
    mov [result_str + 1], ah     ; Сохранить цифру единиц
    mov byte [result_str + 2], 0xA  ; Перевод строки

    ; Вывод сообщения с результатом
    mov eax, 4
    mov ebx, 1
    mov ecx, result_msg
    mov edx, result_msg_len
    int 0x80

    ; Вывод результата вычисления (может состоять из 1 или 2 цифр)
    mov eax, 4
    mov ebx, 1
    mov ecx, result_str
    cmp byte [result_str], '0'
    je  output_one_digit        ; Если разряд десятков равен '0', выводить только разряд единиц
    mov edx, 3                  ; 2 цифры + перевод строки
    jmp do_output

output_one_digit:
    inc ecx                     ; Пропустить разряд десятков '0'
    mov edx, 2                  ; 1 цифра + перевод строки

do_output:
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

Результат выполнения:

$ nasm -f elf32 simple_calc.asm -o simple_calc.o
$ ld -m elf_i386 simple_calc.o -o simple_calc
$ ./simple_calc
Enter first number (0-9): 7
Enter second number (0-9): 8
Sum = 15

В ассемблере обработка числового ввода/вывода требует чёткого понимания ASCII-кодировки и преобразования систем счисления. Преобразование числа в строку (int в string) и строки в число (string в int) — это две самые базовые служебные функции. После написания рекомендуется сохранить их как шаблоны для дальнейшего прямого повторного использования.

Другие расширения