В своё время в практике разработчика довольно часто можно было встретить задачи по преобразованию типов данных, одним из подвидов которых является конвертация шестнадцатеричного числа в строку, иными словами преобразование байта/слова/двойного слова в строку символов ASCIIZ (упрощенно: текстовое представление) для последующего использования. Проблема стара как мир ЭВМ и на низком уровне состоит в последовательной конвертации полубайтов (ниббла, 4 бит) в представление ASCIIZ. Чаще всего полученное представление числа используется для вывода на устройство отображения (экран). И все было бы достаточно тривиально, если бы между шестнадцатеричными значениями (0..F) и соответствующими кодами таблицы (набора) ASCII имелось прямое соответствие, но разработчики стандарта решили иначе, итогом чего явился досадный факт разделения ряда ASCII-кодов символов на границе '9' и 'A' (шестнадцатеричные значения 39h и 41h соответственно). Существующая реальность имеет два независимых ряда: 0-9 и A-F, и именно по этой вот несуразной причине алгоритмы конвертации несколько усложнились.
16-битный код
Если брать старые-добрые времена, то там что называется "под реальным железом" для конвертации шестнадцатеричного числа в строку не было никаких доступных разработчику готовых сервисных функций ни в BIOS, ни в DOS. Поэтому давно ушедшее время 80-90х годов было чрезвычайно богато на различного рода реализации преобразования шестнадцатеричного числа в строку. Самый компактный из полученных мною в своё время алгоритмов был длиною всего 19 байт и представлял следующее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
;----------------------------------------------------------------------------- ; Функция преобразования слова в ASCII представление ; вход: AX = слово для конвертации, ES:DI = смещение буфера, флаг направления (DF) сброшен. ; выход: ES:DI = строка в формате ASCII (4 байта) ; Прим.: Не сохраняет регистры AX/CX/DI ;----------------------------------------------------------------------------- str_word_to_ascii: mov cx, 4 ; в слове 4 ниббла (полубайта) @@: rol ax, 4 ; выдвигаем младшие 4 бита push ax ; сохраним AX and al, 0Fh ; оставляем 4 младших бита AL cmp al, 0Ah ; сравниваем AL со значение 10 sbb al, 69h ; целочисленное вычитание с заёмом das ; BCD-коррекция после вычитания stosb ; помещаем получившийся символ в буфер pop ax ; восстановим AX loop @b ; цикл ret |
За основу функции взят некий алгоритм Эллисона (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).
32-битный код
Под Windows все прозаичнее, ведь под рукой у нас целая "продвинутая" операционная система, глупо было бы не добавить в неё несколько сотен функций :), поэтому тут у нас два пути решения задачи: использовать встроенные системные функции Win32 API либо писать собственную реализацию по преобразованию шестнадцатеричного значения в строку ASCII.
Системные функции
Самая простая идея под Windows состоит в использовании системной функции-форматера под названием wsprintf. Функция wsprintf представляет собой мощнейший системный преобразователь форматов, который создает результирующую строку из значений входных параметров, модифицируя их тип/длину в соответствии с задаваемым специальными спефицикаторами шаблоном вывода. Говоря простым языком, функция берет входные значения, конвертирует их (в соответствии с заданными требованиями) и помещает в выходной буфер. Типичный пример использования:
1 2 3 |
. . . invoke wsprintf,szValue,szFmt,eax . . . |
где:
EAX
задает число для конвертации;- szValue определяет выходной буфер для формирования результата, который описывается в секции данных следующим образом:
1szValue db 8 dup (0) - а szFmt задает формат, к которому приводится входное значение. Тут ключевым моментом является спецификатор формата, который в случае преобразования в шестнадцатеричное текстовое представление выглядит как
%08X
, где число 8 задает разрядность получаемого результата, а X определяет шестнадцатеричное представление. Описывается в секции данных следующим образом:
1szFmt db '%08X',0
После отработки функции wsprintf в выходном буфере (szValue) образуется строка символов с текстовым представлением шестнадцатеричного числа. Только не забудьте в своем проекте импортировать функцию wsprintf из библиотеки USER32.DLL.
Собственная реализация
Если уж совсем хочется пользоваться собственным кодом, либо время исполнения функции критично, то можно воспользоваться и собственным решением. Оно мало чем отличается от своего 16-битного собрата, единственное что преобразована для работы с двойными словами и оптимизировано под соглашение о вызовах stdcall
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
;----------------------------------------------------------------------------- ; Функция преобразования двойного слова в ASCII представление ; Прим.: Не сохраняет регистры EAX/ECX/EDI ;----------------------------------------------------------------------------- proc str_dword_to_ascii hexValue:DWORD, oBuffer:DWORD mov eax, [hexValue] ; EAX = двойное слово для конвертации mov edi, [oBuffer] ; EDI = выходной буфер mov ecx, 8 ; в двойном слове 8 нибблов (полубайт) @@: rol eax, 4 ; выдвигаем младшие 4 бита push eax ; сохраним EAX and al, 0Fh ; оставляем 4 младших бита AL cmp al, 0Ah ; сравниваем AL со значение 10 sbb al, 69h ; целочисленное вычитание с заёмом das ; BCD-коррекция после вычитания stosb ; помещаем получившийся символ в буфер pop eax ; восстановим EAX loop @b ; цикл ret |
А как преобразовать байт в шестнадцатеричное представление?
то же самое, только с одним байтом?
Огромное СПАСИБИЩЕ !
да не за чтоЩЕ, заходите еЩЕ! :)
Я в своё время для преобразования 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
Кусок весь
L1:
add al,30h
jle L2
add al,7
L2:
В принципе можно заменить одной командой
xlat она же xlatb
только в bx указатель на таблицу положить
А можно пояснить, как проверяются границы введенных символов (буква-цифра) здесь:
add dl,09Fh
sub dl,01Ah
jb nextsym
add al,0BFh
sub al,01Ah
jae nextsym+n
команды add/sub воздействуют на флаг CF, команда jb выполняет переход в зависимости от значения флага CF. собственно на этом и построена логика. таким вот хитрым образом, видимо, проверяется попадание в заданный диапазон (цифра/буква) в таблице ASCII.