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

Метки:  ,

В своё время в практике разработчика довольно часто можно было встретить задачи по преобразованию типов данных, одним из подвидов которых является конвертация шестнадцатеричного числа в строку, иными словами преобразование байта/слова/двойного слова в строку символов 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) образуется строка символов с текстовым представлением шестнадцатеричного числа.

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

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

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

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