Шестнадцатеричное число в строку на ассемблере

Метки:  , ,

В своё время в практике разработчика довольно часто можно было встретить задачи по преобразованию типов данных, одним из подвидов которых является конвертация шестнадцатеричного числа в строку, иными словами преобразование байта/слова/двойного слова в строку символов ASCIIZ (упрощенно: текстовое представление) для последующего использования. Проблема стара как мир ЭВМ и на низком уровне состоит в последовательной конвертации полубайтов (ниббла, 4 бит) в представление ASCIIZ. Чаще всего полученное представление числа используется для вывода на устройство отображения (экран). И все было бы достаточно тривиально, если бы между шестнадцатеричными значениями (0..F) и соответствующими кодами таблицы (набора) ASCII имелось прямое соответствие, но разработчики стандарта решили иначе, итогом чего явился досадный факт разделения ряда ASCII-кодов символов на границе '9' и 'A' (шестнадцатеричные значения 39h и 41h соответственно). Существующая реальность имеет два независимых ряда: 0-9 и A-F, и именно по этой вот несуразной причине алгоритмы конвертации несколько усложнились.

16-битный код

Если брать старые-добрые времена, то там что называется "под реальным железом" для конвертации шестнадцатеричного числа в строку не было никаких доступных разработчику готовых сервисных функций ни в BIOS, ни в DOS. Поэтому давно ушедшее время 80-90х годов было чрезвычайно богато на различного рода реализации преобразования шестнадцатеричного числа в строку. Самый компактный из полученных мною в своё время алгоритмов был длиною всего 19 байт и представлял следующее:

За основу функции взят некий алгоритм Эллисона (Allison's Algorithm), который бесспорно гениален, но совершенно не интуитивен, поскольку в нем используется довольно редко применяемая на практике, а главное достаточно непростая в логике, инструкция das.
Пояснение к алгоритму функции:

  • производим обнуление старших четырех битов регистра AL, поскольку будем работать с младшими четырьмя;
  • получившееся значение в регистре AL сравнивается с 0Ah (10): если значение регистра меньше, то выставляется флаг переноса (CF), если больше, то флаг сбрасывается;
  • инструкция sbb производит "целочисленное вычитание с заёмом" из регистра AL значения 69h, учитывая при этом (изменившееся ранее) значение флага переноса;
  • инструкция das выполняет "десятичную коррекцию после вычитания". Предназначается для исполнения сразу после команд sub/sbb и выполняет коррекцию результата вычитания (разности) двух упакованных двоично-десятичных чисел (BCD). Корректирует содержимое регистра AL так, что бы в результате выполнения получилось двухзначное упакованное двоично-десятичное число.

Вот именно описанная выше логика у нас и обыгрывает разрыв в таблице ASCII и обеспечивает преобразование двух шестнадцатеричных рядов (0-9 и A-F) в коды соответствующих символов в таблице ASCII. При вызове функции подразумевается, что флаг направления (DF) выставлен в 0, то есть задано направление "вперед" (для более универсального кода можно в начале функции дописать команду cld).

Функция достаточно шустрая, тем не менее, для увеличения быстродействия я бы развернул инструкцию loop в пару dec/jnz. Это увеличит размер функции на 1 байт, однако добавит скорости на большинстве процессоров.

32-битный код

Под Windows все прозаичнее, ведь под рукой у нас целая "продвинутая" операционная система, глупо было бы не добавить в неё несколько сотен функций :), поэтому тут у нас два пути решения задачи: использовать встроенные системные функции Win32 API либо писать собственную реализацию по преобразованию шестнадцатеричного значения в строку ASCII.

Системные функции

Самая простая идея под Windows состоит в использовании системной функции-форматера под названием wsprintf. Функция wsprintf представляет собой мощнейший системный преобразователь форматов, который создает результирующую строку из значений входных параметров, модифицируя их тип/длину в соответствии с задаваемым специальными спефицикаторами шаблоном вывода. Говоря простым языком, функция берет входные значения, конвертирует их (в соответствии с заданными требованиями) и помещает в выходной буфер. Типичный пример использования:

где:

  • EAX задает число для конвертации;
  • szValue определяет выходной буфер для формирования результата, который описывается в секции данных следующим образом:
  • а szFmt задает формат, к которому приводится входное значение. Тут ключевым моментом является спецификатор формата, который в случае преобразования в шестнадцатеричное текстовое представление выглядит как %08X, где число 8 задает разрядность получаемого результата, а X определяет шестнадцатеричное представление. Описывается в секции данных следующим образом:

После отработки функции wsprintf в выходном буфере (szValue) образуется строка символов с текстовым представлением шестнадцатеричного числа. Только не забудьте в своем проекте импортировать функцию wsprintf из библиотеки USER32.DLL.

Собственная реализация

Если уж совсем хочется пользоваться собственным кодом, либо время исполнения функции критично, то можно воспользоваться и собственным решением. Оно мало чем отличается от своего 16-битного собрата, единственное что преобразована для работы с двойными словами и оптимизировано под соглашение о вызовах stdcall:

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

  1. Елена

    А как преобразовать байт в шестнадцатеричное представление?

    1. einaare

      то же самое, только с одним байтом?

  2. Евгений

    Огромное СПАСИБИЩЕ !

    1. einaare

      да не за чтоЩЕ, заходите еЩЕ! :)

  3. Бертыш

    Я в своё время для преобразования AX делал рекурсивный вариант
    xchg ah,dl
    call L0
    xchg ax,dx
    L0:
    ;Далее недокументированная команда процессора aam 10h
    aam
    org $-1
    db 10h
    call L1
    xchg ah,al
    L1:
    add al,30h
    jle L2
    add al,7
    L2:
    ret

  4. Бертыш

    Кусок весь
    L1:
    add al,30h
    jle L2
    add al,7
    L2:

    В принципе можно заменить одной командой
    xlat она же xlatb
    только в bx указатель на таблицу положить

  5. Алексей

    А можно пояснить, как проверяются границы введенных символов (буква-цифра) здесь:
    add dl,09Fh
    sub dl,01Ah
    jb nextsym
    add al,0BFh
    sub al,01Ah
    jae nextsym+n

    1. einaare

      команды add/sub воздействуют на флаг CF, команда jb выполняет переход в зависимости от значения флага CF. собственно на этом и построена логика. таким вот хитрым образом, видимо, проверяется попадание в заданный диапазон (цифра/буква) в таблице ASCII.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *