x64 software conventions
будем считать что указатель на начало Числовой строки подлежащие конвертированию расположено в RCX
.
x64
битный код при x32
битной адресации. Такой способ адресации позволяет использовать преимущества обоих диалектов. Для установки указанного режима необходимо указать директиву /LARGEADDRESSAWARE:NO
в линковщику.
; псевдонимы операндов #region BUFF_STR equ esp - xmmword * 4 ; #endregion
DWORD
или INT
для тех кому более привычен синтаксис СРР
которые не имеют своего собственного отображения в памяти, а все время своего существования размещаются в регистре с которым они ассоциированы, при этом некоторые «переменны» являются по сути «объединениями» и размещаются в одних и тех же регистрах присутствуя в них на разных этапах исполнения программы:
; псевдонимы регистров #region CUR_CHAR equ ecx ; абсолютная позиция текущего символа DOT_CHAR equ edx ; относительная позиция символа точки HASH_STR equ r8d ; хеш символов Числовой строки END_CHAR equ HASH_STR ; относительная позиция последнего символа N_Z_CHAR equ r9d ; относительная позиция символ не нулевого числа OFF_CHAR equ N_Z_CHAR ; смешение дробной части относительно начала Числовой строки END_FRAC equ r10d ; относительное положение последнего символа Числовой строки EXP_CHAR equ END_FRAC ; текущий относительный символ строки Экспоненты LEN_NUMB equ r11d ; длина значимой части Числа LEN_CELL equ LEN_NUMB ; длина целой части Числа HASH_MUL equ ebx ; значение экспоненты в десятичной системе MANT_ARG equ r8 ; мантисса аргумент множителя LOGB_ARG equ r9d ; порядок аргумента множителя MANT_MUL equ r10 ; мантисса множителя LOGB_MUL equ r11d ; порядок множителя ; #endregion
SIMD
команды не допускают непосредственной размещения данных в инструкциях секции кода, что вынуждает создавать секцию данных:
.data ; #region Xmm_HT byte 10h dup (09h) Xmm_CR byte 10h dup (0Dh) Xmm_SP byte 10h dup (20h) Xmm_SL byte 10h dup ('/') Xmm_30 byte 10h dup ('0') Xmm_39 byte 10h dup ('9') Xmm_0001 word 8 dup (010Ah) Xmm_0010 dword 4 dup (10064h) Xmm_0100 qword 2 dup (100002710h) Mask_001 word 0044h, 0944h, 0D44h, 2044h, 0046h, 0946h, 0D46h, 2046h Mask_010 word 0064h, 0964h, 0D64h, 2064h, 0066h, 0966h, 0D66h, 2066h Mul_0001 qword 0E8D4A51000h Plus word 2B00h ; тестовая строка string byte ' ', 0Dh, 0Ah, '+-0098765432109876540.09876e-0248 ' ; #endregion
Назначение определенных констант будет пояснено ниже в ходе выполнения процедуры.
ХММ3
самими собой в результате чего все байт ХММ3
принимают значение -1
.— уменьшаем указатель адреса первого символа в
CUR_CHAR
на длину ХММ
регистра.— увеличиваем указатель адреса первого символа в
CUR_CHAR
на длину ХММ
регистра.
pcmpeqb xmm3, xmm3 sub CUR_CHAR, xmmword @@: add CUR_CHAR, xmmword
Таким образом при начальном входе в цикл обработки указатель текущего символа будет установлен на начало строки, при последующих входах в цикл он будет смещаться на длину ХММ-регистра
, то есть на 15 байт. Такой способ организации начала цикла, при котором инкремент расположен в начале цикла, позволяет значительно упростить выход из цикла сведя его к команде проверки условия и условному переходу. В противном случае при размещении команда инкремента и проверки условий в конце цикла эти инструкции конфликтовали бы в части изменения флагов процессора что избыточно усложнило бы выход из цикла.
ХММ0
и копируем ее в ХММ1
/ХММ2
получая три копии строки.— сравниваем три копии строки содержащиеся в регистрах с тремя строками размещенными в памяти, равномерно заполненными символами
пробел
/табуляция
/возврат каретки
— складываем полученные результаты в регистр
ХММ0
.
movdqu xmm0,[CUR_CHAR] movdqa xmm1, xmm0 movdqa xmm2, xmm0 pcmpeqb xmm0, xmmword ptr Xmm_SP pcmpeqb xmm1, xmmword ptr Xmm_HT pcmpeqb xmm2, xmmword ptr Xmm_CR paddb xmm0, xmm1 paddb xmm0, xmm2
В результате байты регистра ХММ0
равные любому из трех символов обобщенного пробела принимают значение -1
а не равные 0
. Векторное сравнение позволяет многократно повысить скорость сканирования строки не только за счет параллельного сравнения но и за счет исключения множества условных переходов характерных для «классических» способов.
PTEST
выполняет операцию AND
над байтами ХММ0
и ХММ3
и в случае если все байты результата установлены в -1
устанавливаем флаг переноса CF=1
.— если флаг переноса
CF=1
то следовательно в сканируемой строке отсутствуют символы отличные от обобщенного пробела и необходимо вернуться в начало цикла.
ptest xmm0, xmm3 jc @b ; повторный пропуск обобщенного пробела ; #endregion
В результате сканирование строки продолжается до тех пор пока не будет найден символ отличный от обобщенного пробела. Скорость сканирования можно дополнительно увеличить если разместить строки равномерно заполненные символами пробел
/табуляция
/возврат каретки
в старших регистрах SIMD
, но в соответствии с соглашением вызова х64
это потребует предварительно сохранить их значение в память, а при выходе из функции восстановить, что учитывая ожидаемое время сканирования в один проход будет не оправданно.
ХММ0
в EAX
, теперь все биты соответствующие символам обобщенного пробела установлены в значение 1
.— инвертируем
EAX
, теперь биты соответствующие символам не равным обобщенному пробелу установлен в значение 1
.— сканируем биты регистра
EAX
от младшего к старшему в поиске первого бита установлено в значение 1
, и результат равный номеру бита, помещаем в этот же регистр.— добавляем значение
EAX
к CUR_CHAR
и получаем указатель на первый символ отличный от обобщенного пробела.
; Позиция первого символа не равного обобщенному пробелу #region pmovmskb eax, xmm0 not eax bsf eax, eax add CUR_CHAR, eax ; #endregion
ZF=1
если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов новая строка
.— устанавливаем младший байт регистра
EAX
в значение 1
если флаг нуля ZF=1
и в значение 0
при всех остальных вариантах.— складываем значение
EAX
и CUR_CHAR
и получаем указатель на первый символ отличный от обобщенного пробела с учетом сочетания символов новой строки
.
; позиция первого символа не равного обобщенному пробелу #region cmp word ptr[CUR_CHAR - byte], 0A0Dh setz al add CUR_CHAR, eax ; #endregion
Таким образом если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов новая строка
то второй символ после обобщённого пробела будет проигнорирован, в противном случае он будет подвергнут дальнейшему анализу.
EAX
одновременно расширяя его до двойного слова.— устанавливаем флаг нуля
ZF=1
если значение EAX
равно 0
.— если флаг нуля
ZF=1
то следовательно имеет место обрыв строки и необходимо выйти из процедуры вернув код ошибки:
; тест обрыва строки #region movzx eax, byte ptr[CUR_CHAR] test al, al jz ErrorExit ; обрыв строки ; #endregion
AL
с символом плюс
.— устанавливаем
AL
в значение 1
если AL
равен символу плюс
и 0
при любом другом значении символа.— добавляем значение
EAX
к CUR_CHAR
.
; Проверка символа минус/плюс #region cmp al, '+' setz al add CUR_CHAR, eax
В результате если текущий символ равен символу плюс
то позиция текущего символа будет смещена на следующий символ, во всех остальных случаях символ будет подвергнут повторному анализу.
минус
.— устанавливаем значение
AL
в 1
если текущий символ равен символу минус
и 0
при любом другом значении.— добавляем значение
EAX
к CUR_CHAR
.— добавляем значение
EAX
к регистру ESP
.
cmp byte ptr[CUR_CHAR], '-' setz al add CUR_CHAR, eax add esp, eax ; #endregion
В результате если текущий символ равен символу минус
то позиция текущего символа будет смещена на следующий символ, а значение регистра стека ESP
увеличено на 1
, во всех остальных случаях символ будет подвергнут повторному анализу, а значение регистра стека останется без изменений. Прямое изменение значения регистра указателя стека ESP
считается крайне опасным действием чреватым непредсказуемыми ошибками, но я практикую «агрессивный» подход и считаю что не бывает «плохого» или «хорошего» кода, бывают хорошие и плохи программисты, хорошие пишут так как будто никаких правил нет вообще но результат при этом такой как будто они соблюдают их все, а плохие они просто плохие.
ХММ0
.— копируем старшую часть Числовой строки в
ХММ1
и ХММ2
.— сравниваем регистр
ХММ0
со строкой в памяти равномерно заполненной символами 9
.— копируем регистр
ХММ0
в регистр ХММ
1.
; сканирование символов Числовой строки #region movdqu xmm0,[CUR_CHAR + xmmword] movdqa xmm2, xmm0 movdqa xmm3, xmm0 pcmpgtb xmm0, xmmword ptr Xmm_39 movdqa xmm1, xmm0
В результат получаем две копии строки в регистрах ХММ0
и ХММ1
в которых все байты символов которые были больше символа 9
установлены в значение -1
, а все остальные в значение 0
.
ХММ2
со строкой в памяти равномерно заполненной символами косая черта
, в результате чего все байты регистра ХММ2
содержавшие символы больше и равно символу 0
установлены в значение -1
, а меньше в значение 0
. — командой
PANDN
инвертируем баты регистра ХММ0
и выполняем логическую операцию AND
над байтами ХММ0
и ХММ2
помещая результат в регистр ХММ0
.
pcmpgtb xmm2, xmmword ptr Xmm_SL pandn xmm0, xmm2
В результате все байты регистра ХММ0
содержащие символы в диапазоне от 0
включительно до 9
включительно, то есть цифры
, принимают значения -1
а все остальные 0
.
ХММ3
со строкой в памяти равномерно заполненной символами 0
, в результате чего все байты регистра ХММ3
содержавшие символы больше и равно символу 1
установлены в значение -1
, а меньше в значение 0
. — командой
PANDN
инвертируем баты регистра ХММ1
и выполняем логическую операцию AND
над байтами ХММ1
и ХММ3
помещая результат в регистр ХММ1
.
pcmpgtb xmm3, xmmword ptr Xmm_30 pandn xmm1, xmm3
В результате все байты регистра ХММ1
содержащие символы в диапазоне от 1
включительно до 9
включительно, то есть значащие цифры
, принимают значения -1
а все остальные 0
.
ХММ0
в регистр HASH_STR
.— копируем старшие биты байтов регистра
ХММ1
в регистр N_Z_CHAR
pmovmskb HASH_STR, xmm0 pmovmskb N_Z_CHAR, xmm1
В результате младшие 16 бит регистра HASH_STR
соответствуют 16 старшим байтам Числовой строки, при этом биты соответствующие символам содержащим цифры принимают значения 1
а все остальные 0
, а младшие 16 бит регистра N_Z_CHAR
соответствуют 16 старшим байтам Числовой строки, при этом биты соответствующие символам содержащим значащие числа, принимают значения 1
а все остальные 0
.
movdqu xmm0,[CUR_CHAR] movdqa xmm2, xmm0 movdqa xmm3, xmm0 pcmpgtb xmm0, xmmword ptr Xmm_39 movdqa xmm1, xmm0 pcmpgtb xmm2, xmmword ptr Xmm_SL pcmpgtb xmm3, xmmword ptr Xmm_30 pandn xmm0, xmm2 pandn xmm1, xmm3
ХММ0
в EAX
.— сдвигаем младшие 16 бит
HASH_STR
в старшую часть HASH_STR
.— складываем значение
HASH_STR
и EAX
.— копируем старшие биты байтов регистра
ХММ1
в EAX
.— сдвигаем младшие 16 бит
N_Z_CHAR
в старшую часть N_Z_CHAR
.— складываем значение
N_Z_CHAR
и EAX
.
pmovmskb eax, xmm0 shl HASH_STR, xmmword add HASH_STR, eax pmovmskb eax, xmm1 shl N_Z_CHAR, xmmword add N_Z_CHAR, eax
В результате HASH_STR
содержит хеш Числовой строки в котором биты соответствующие символам цифр
установлены в значение 1
а в се остальные в 0
, при этом номера битов соответствуют номерам символов от начала строки начиная с нуля, а N_Z_CHAR
содержит хеш Числовой строки в котором биты символов соответствующие значащих цифр
установлены в значение 1
, а все остальные в 0
, при этом номер бита соответствуют номерам символов от начала строки начиная с нуля.
HASH_STR
от младшего бита к старшему в поисках первого бита равного 1, результат помещаем в EAX
и устанавливаем флаг нуля ZF=1
если все биты равны нулю.— если флаг нуля
ZF=1
то значит строка не содержит ни одного символа цифры
и необходимо выйти из процедуры вернув код ошибки.— устанавливаем флаг нуля
ZF=0
если полученный результат отличен от нуля.— если флаг нуля
ZF=0
то значит первый символ строки не является цифрой
и необходимо выйти из процедуры вернув код ошибки.
; проверка первого символа #region bsf eax, HASH_STR jz ErrorExit test eax, eax jnz ErrorExit ; первый символ не цифра ; #endregion
В результат проверяем содержит ли Числовой строки хотя бы один символ цифры
и является ли первый символ Числовой строки цифрой
. Особенностью данного участка кода в нестандартном поведении инструкции BSF
которая проявляется в работе с флагом нуля, а именно если при сканирование первым битом установленным в значение 1
окажется бит с порядковым номером 0
то BSF
установит значение регистра назначения в 0
но при этом установит флаг нуля ZF=0
как будто в регистре содержится число отличное от нуля, если же инструкция не обнаружит ни одного бита в значении 1
, то регистр назначение не будет подвергнут изменению а флаг нуля будет установлен в ZF=1
.
HASH_STR
в результате чего теперь каждый бит установленный в 1
сигнализирует о символе НЕ цифре
.— сканируем
HASH_STR
от младшего бита к старшему, результат помещаем в DOT_CHAR
и устанавливаем флаг нуля ZF=1
если все биты HASH_STR
равны нулю.— если флаг нуля
ZF=1
то значит строка не содержит ни одного символа отличного от цифры
и необходимо выйти из процедуры вернув код ошибки.— сравниваем символ отличный от
цифры
с символом точка
и устанавливаем флаг ZF=0
если они не равны.— если флаг нуля
ZF=0
то значит первый символ отличный от цифры
не равен символу точка
и необходимо выйти из процедуры вернув код ошибки.
; поиск символа точки разделяющий целую и дробную части Числа #region not HASH_STR bsf DOT_CHAR, HASH_STR jz ErrorExit ; точки не обнаружено cmp byte ptr[CUR_CHAR + DOT_CHAR], '.' jnz ErrorExit ; символ не является точкой ; #endregion
N_Z_CHAR
в EAX
— сканируем
N_Z_CHAR
от младшего бита к старшему и помещаем результат в этот же регистр.— сохраняем в память строку из четырех нулей
0000
по адресу на 1 (один) байт меньше адреса указанного в BUFF_STR
.— сохраняем в регистр
ХММ0
старшую часть строку символов начинающийся с первого символа значащей цифры
, на который указывает N_Z_CHAR
, игнорирую таким образом ведущие нули.— сохраняем в память старшую часть строки символов по адресу указанному в
BUFF_STR
.— сохраняем в регистр
ХММ0
младшую часть строки символов на которую указывает N_Z_CHAR
со смещение в 16 байт.— сохраняем в память младшую часть строки символов начиная с первого символа
значащей цифры
по адресу указанному в BUFF_STR
со смещение в 16 байт.
; сохранение значащей части Числа #region mov eax, N_Z_CHAR bsf N_Z_CHAR, N_Z_CHAR mov dword ptr[BUFF_STR - byte], 30303030h movdqu xmm0,[CUR_CHAR + N_Z_CHAR] movdqu [BUFF_STR + 00000000], xmm0 movdqu xmm0,[CUR_CHAR + N_Z_CHAR + xmmword] movdqu [BUFF_STR + 00000000 + xmmword], xmm0 ; #endregion
В результате сохраняем в память строку из 32 символов начиная с первого символа значащей цифры
на которую указывает N_Z_CHAR
по адресу указанному в BUFF_STR
. При этом указанная строка может содержать символ точки и иные символы не относящиеся к цифрам.
DOT_CHAR
в регистр ХММ0
.— загружаем младшую часть Числовой строку, следующую сразу после точки, на которую указывает
DOT_CHAR
со смещение 16 байт от начала Числовой строки в регистр ХММ1
.
; загрузка дробной части Числовой строки #region movdqu xmm0,[CUR_CHAR + DOT_CHAR + byte] movdqu xmm1,[CUR_CHAR + DOT_CHAR + byte + xmmword] ; #endregion
HASH_STR
бит указанный в DOT_CHAR
удаляя его из хеша, теперь при следующем сканировании бит указывающий на точку будет проигнорирован.— сканируем
HASH_STR
от младшего бита к старшему помещая результат в этот же регистр устанавливая флаг нуля ZF=1
если все биты равны нулю.— если флаг нуля
ZF=1
то значит дробная часть строки не имеет корректного окончания и необходимо выйти из процедуры вернув код ошибки.
; поиск конца дробной части Числа #region btr HASH_STR, DOT_CHAR bsf END_FRAC, HASH_STR jz ErrorExit ; #endregion
В результате в EXP_CHAR
находиться указатель на первый символ экспоненты или окончание Числа относительно начала Числа.
END_FRAC
и N_Z_CHAR
и устанавливаем флаг переполнения CF=1
если N_Z_CHAR
больше END_FRAC
.— копируем
END_FRAC
в N_Z_CHAR
если CF=1
.
; количество значащих символов Числа #region cmp END_FRAC, N_Z_CHAR cmovc N_Z_CHAR, END_FRAC
В результате если и целая и дробная часть Числа состоят из одних нулей а первый символ значащей цифры
находиться за пределами дробной и целой части числа, о чем свидетельствует факт того что N_Z_CHAR
больше END_FRAC
, то присваиваем N_Z_CHAR
значение END_FRAC
то есть указателя на первый символ экспоненты или окончания числа.
N_Z_CHAR
и DOT_CHAR
и если N_Z_CHAR
меньше DOT_CHAR
, то есть первая значащая цифра расположен раньше точки, что означает что у числа существует целая часть, устанавливаем флаг переноса CF=1
.— копируем в
LEN_NUMB
указатель на первый символ экспоненты или окончания Числа содержащийся в END_FRAC
.— вычитаем из
LEN_NUMB
указатель на первую значащую цифру содержащуюся в N_Z_CHAR
и флаг переноса CF
.
cmp N_Z_CHAR, DOT_CHAR mov LEN_NUMB, END_FRAC sbb LEN_NUMB, N_Z_CHAR ; #endregion
В результате в LEN_NUMB
содержится значение количества цифр Числа начиная с первой значащей цифры без учета символа точки, то есть исключительно количество символов соответствующих цифрам, символ точки в подсчете не учитывается даже если число пересекает точку.
DOT_CHAR
значение N_Z_CHAR
и устанавливаем флаг знака SF=0
, если полученное число положительное.— помещаем в
OFF_CHAR
число 20
равное количеству символов которое будет в дальнейшем использованы для создания мантиссы.— Если флаг знака
SF=0
то значит число имеет целой части и необходимо скопировать DOT_CHAR
в OFF_CHAR
.— сохраняем в память старшую часть строки следующей сразу за символом
точки
со смещением указанным в OFF_CHAR
по адресу указанному в BUFF_STR
.— сохраняем в памяти младшую часть строки следующей сразу за символом
точки
со смещением указанным в OFF_CHAR
плюс 16 байт, по адресу указанному в BUFF_STR
; сохранение дробной части Числа #region sub DOT_CHAR, N_Z_CHAR mov OFF_CHAR, xmmword + dword cmovns OFF_CHAR, DOT_CHAR movdqu xmmword ptr[BUFF_STR + OFF_CHAR + 0000000], xmm0 movdqu xmmword ptr[BUFF_STR + OFF_CHAR + xmmword], xmm1 ; #endregion
В результате если число имеет целую часть то в OFF_CHAR
помещается длина целой части, в противном случае в OFF_CHAR
помещается длина Числа по умолчанию равная 20
байтам. Таким образом таким образом если у числа есть целая и дробная часть они будут склеены в единую строку с удалением символа «точки» между ними, если число имеет только целую или только дробную часть, то строка символов начинающаяся после точки будет сохранена за пределами сканируемой строки и таким образом проигнорирована.
ХММ2
строку символов ноль
.— сохраняем в память строку символов
ноль
со смещением указанным в LEN_NUMB
по адресу указанному в BUFF_STR
.— сохраняем в память строку символов
ноль
со смещением указанным в LEN_NUMB
плюс 16 байт, по адресу указанному в BUFF_STR
.— помещаем в
LEN_CELL
удвоенное значение DOT_CHAR
то есть удвоенную длину целой части числа.
; зануление недостающих символов Числа #region movdqu xmm2, xmmword ptr Xmm_30 movdqu xmmword ptr[BUFF_STR + LEN_NUMB + 0000000], xmm2 movdqu xmmword ptr[BUFF_STR + LEN_NUMB + xmmword], xmm2 lea LEN_CELL, [DOT_CHAR * 2] ; #endregion
В результат все «мусорные» символы Числовой строки, находящиеся после последнего символа цифры
, на который указывает LEN_NUMB
будут заменены символами нуля
. Таким образом в памяти будет сформирована единая непрерывная строка начинающаяся с первого значащего символа, содержащая все значимые цифры и дополненная нуля в случае если значимая часть Числа меньше 20
символов. Кроме того в LEN_CELL
будет помещена удвоенная сумма знаком между символом точки
и первой значащей цифрой
.
N_Z_CHAR
.— загружаем в младшее двойное слова регистра
ХММ0
четыре символа начиная с символа который следует за последним символом дробной части Числовой строки и на который указывает END_FRAC
.— копируем два символа на которые указывает
END_FRAC
в четыре младших слова регистра ХММ0
и получаем четыре копии пары символов окончания числа.— копируем два символа на которые указывает
END_FRAC
во все слова регистра ХММ0
и получаем восемь копии копий пары символов окончания числа.— копируем
ХММ0
в ХММ1
и получаем шестнадцать копий пары символов окончания числа.
; проверка корректного окончания числа #region xor N_Z_CHAR, N_Z_CHAR movd xmm0, dword ptr[CUR_CHAR + END_FRAC] pshuflw xmm0, xmm0, 0 pshufd xmm0, xmm0, 0 movdqa xmm1, xmm0
ХММ0
со строкой символов в памяти содержащей восемь вариантов окончания Числовой строки и устанавливаем значение совпадающих слов в -1
а в остальных случаях в 0
.— сравниваем слова регистра
ХММ1
со строкой символов в памяти содержащей восемь вариантов окончания Числовой строки и устанавливаем значение совпадающих слов в -1
а в остальных случаях в 0
.— складываем значение
ХММ0
и ХММ1
и помещаем результат в регистр ХММ0
.— сравниваем байты регистра
ХММ1
самими собой в результате чего все байт ХММ1
принимают значение -1
.— командой
PTEST
выполняет операцию AND
над словами ХММ0
и ХММ1
и если хотя бы одно слово установлены в -1
устанавливаем флаг нуля ZF=0
.— если флаг нуля ZF=0 то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой.
pcmpeqw xmm0, Mask_001 pcmpeqw xmm1, Mask_010 paddw xmm0, xmm1 pcmpeqb xmm1, xmm1 ptest xmm0, xmm1 jnz @f
EDX
два символа начиная с символа который следует за последним символом дробной части Числовой строки и на который указывает END_FRAC
.— устанавливаем флаг нуля
ZF=1
если младший байт регистра EDX
равен 0
.— если флаг нуля
ZF=1[/INLINE то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой.
movzx edx, word ptr[CUR_CHAR + END_FRAC] test dl, dl jz @f
5
в регистре EDX
в результате чего если в регистре содержатся символы строчных букв они будут преобразованы в прописные.— устанавливаем флаг нуля
ZF=0
если значение младшего байт регистра EDX
НЕ равно значению символа Е
.— если флаг нуля
ZF=0
то значит числовая строка содержит критическую ошибку в оформлении и необходимо выйти из процедуры вернув код ошибки.
; проверка символа экспоненты #region btr edx, 5 cmp dl,'E' jnz ErrorExit ; #endregion
HASH_STR
бит указанный в EXP_CHAR
удаляя его из хеша, теперь при следующем сканировании бит указывающий на символ экспоненты Е
будет проигнорирован.— увеличиваем значение
EXP_CHAR
на 1
перемещая указатель на следующий символ экспоненты.— сбрасываем в
HASH_STR
бит указанный в EXP_CHAR
удаляя его из хеша, теперь при следующем сканировании бит указывающий на символ знака экспоненты плюс
/минус
будет проигнорирован и устанавливаем флаг переноса CF=1
если значение бита было 0
что означает что знак экспоненты отсутствовал.— если флаг переноса
CF=1
то значит символ знака экспоненты отсутствует в Числовой строке и необходимо загрузить в младшее слово регистра EDX
символ плюс
содержащийся в константе Plus
.— складываем значение указателя на текущий символ экспоненты
EXP_CHAR
с флагом переноса CF=1
для учета позиции символа знака экспоненты при ее наличии.
; проверка знака экспоненты #region btr HASH_STR, EXP_CHAR inc EXP_CHAR btr HASH_STR, EXP_CHAR cmovnc dx, Plus adc EXP_CHAR, 0
Таким образом если числовая строка не содержит знака экспоненты то для обработки будет принудительно загружен символ плюс
при этом позиция текущего символа останется на месте. В случае наличия наличия знака экспоненты указатель на текущий символ будет перемещен на следующий символ после символа знака.
ZF=1
если значение регистра DH
равно символу плюс
.— устанавливаем регистр
DL
в значение 1
если флаг нуля ZF=1
.— устанавливаем флаг нуля
ZF=1
если значение регистра DH
равно символу минус
.— устанавливаем регистра
DH
в значение 1
если флаг нуля ZF=1
.— копируем значение бита номер
8
регистра DX
во флаг переноса CF
.— складываем
LEN_CELL
и флаг переноса CF
.— устанавливаем флаг нуля
ZF=1
если значение регистр EDX
равно 0
.— если флаг нуля
cmp dh,'+' setz dl cmp dh,'-' setz dh bt dx, 8 adc LEN_CELL, 0 ; #endregion
Таким образом информация о знаке экспоненты сохраняется в нулевом бите LEN_CELL
, учитывая что LEN_CELL
изначально хранит удвоенное значение количество символов между символом точки
и первым символом значащего числа
то его нулевой бит всегда имеет нулевое значение и загрузка в него символа знака экспоненты не исказит значение.
EAX
в N_Z_CHAR
восстанавливая значение N_Z_CHAR
ранее сохраненное в EAX
в пункте 4.4.3.
— обнуляем регистр
EAX
.— устанавливаем в
EAX
бит номер которого указан в EXP_CHAR
и который соответствует номеру символа следующего сразу после знака экспоненты если он есть или символа экспоненты если знак экспоненты отсутствует.— складываем значение регистра
EAX
и целое числа, размером в двойное слово, со значением -1
и получаем в EAX
значение в котором все биты соответствующие символам до символа указанного в EXP_CHAR
принимают значение 1
а после значение 0
.— инвертируем значение
EAX
теперь все биты установлены в 1
соответствуют символам следующим после знака экспоненты не включая его.—
; Позиция первого не нулевого символа экспоненты #region mov N_Z_CHAR, eax xor eax, eax bts eax, EXP_CHAR add eax, -1 not eax and N_Z_CHAR, eax bsf N_Z_CHAR, N_Z_CHAR movdqu xmm0,[CUR_CHAR + N_Z_CHAR] ; #endregion
; проверка окончания строки экспоненты #region bsf END_CHAR, END_CHAR jz ErrorExit ; окончание отсутствует movzx eax, byte ptr[CUR_CHAR + END_CHAR] cmp eax, 20h ja ErrorExit add rdx,(1 + 1 shl 09h + 1 shl 0Dh + 1 shl 20h) bt rdx, rax jnc ErrorExit ; #endregion
; #region sub N_Z_CHAR, END_CHAR cmp N_Z_CHAR, -4 ; jnc ErrorExit @@: cmp byte ptr[BUFF_STR + 00000000 + xmmword + dword - byte],'5' mov dword ptr[BUFF_STR + 00000000 + xmmword + dword - byte],'0000' movd dword ptr[BUFF_STR + N_Z_CHAR + xmmword + qword - byte], xmm0 ; #endregion
; вычисление экспоненты и младшей части Числа #region movdqu xmm0,[BUFF_STR + 0000000 - byte] movdqu xmm1,[BUFF_STR + xmmword - byte] psubb xmm0, xmm2 psubb xmm1, xmm2 pmaddubsw xmm1, xmmword ptr Xmm_0001 pmaddwd xmm1, xmmword ptr Xmm_0010 ; #endregion
; вычисление множителя #region movd rax, xmm1 sbb rax, -1 movd xmm1, eax shr rax, 20h movd xmm2, rbx mov ebx, eax neg eax sar LEN_CELL, 1 cmovc ebx, eax add ebx, LEN_CELL mov eax, ebx neg eax cmovns ebx, eax mov rax, 0A000000000000000h mov MANT_ARG, 0CCCCCCCCCCCCCCCCh cmovs MANT_ARG, rax mov eax, 3 mov LOGB_ARG, -3 cmovs LOGB_ARG, eax mov MANT_MUL, 1 mov LOGB_MUL, 0 shr HASH_MUL, 1 cmovc MANT_MUL, MANT_ARG cmovc LOGB_MUL, LOGB_ARG @@: jz @f mov rax, MANT_ARG mul rax bt rdx, 3Fh setnc cl adc LOGB_ARG, LOGB_ARG shld rdx, rax, cl mov MANT_ARG, rdx shr HASH_MUL, 1 jnc @b mov rax, rdx mul MANT_MUL bt rdx, 3Fh setnc cl adc LOGB_MUL, LOGB_ARG shld rdx, rax, cl mov MANT_MUL, rdx test HASH_MUL, HASH_MUL jmp @b @@: movd rbx, xmm2 ; #endregion
; вычисление целой части Числа #region psubb xmm0, xmm2 pmaddubsw xmm0, xmmword ptr Xmm_0001 pmaddwd xmm0, xmmword ptr Xmm_0010 pmulld xmm0, xmmword ptr Xmm_0100 phaddd xmm0, xmm0 movd eax, xmm0 imul rax, Mul_0001 pextrd edx, xmm0, 1 imul rdx, 02710h add rax, rdx movd edx, xmm1 add rax, rdx bsr rcx, rax add LOGB_MUL, ecx inc cl shrd rax, rax, cl ; #endregion
; вычисление числа #region mul MANT_MUL bt rdx, 3Fh setnc cl adc LOGB_MUL, 3FFh shld rdx, rax, cl shl rdx, 1 shrd rdx, r11, 11 shrd rdx, rsp, 1 btr esp, 0 movd xmm0, rdx ; #endregion
ret
ErrorExit: ; аварийный выход #region mov ecx, -1 pcmpeqb xmm1, xmm1 psllq xmm1, 52 + 1 psrlq xmm1, 1 ret ; #endregion
ссылка на оригинал статьи https://habr.com/ru/post/549270/
Добавить комментарий