Преобразование строки символов в число двойной точности на MASM64 без FPU на SSE4.1 форматированное по правилам ML64.EXE то есть длиной до 32 символов.
1. Настройки компиляции, адресации и соглашения о вызовах.
1.1. Передача параметров в процедуру и обратно.
В соответствии с
x64 software conventions
будем считать что указатель на начало Числовой строки подлежащие конвертированию расположено в RCX
.
1.2. Адресация и размерность кода.
Будем использовать
x64
битный код при x32
битной адресации. Такой способ адресации позволяет использовать преимущества обоих диалектов. Для установки указанного режима необходимо указать директиву /LARGEADDRESSAWARE:NO
в линковщику.
2. Текстовые константы и псевдонимы.
2.1. Текстовые константы
Для удобства работы со стеком создаем текстовую константу которая по сути выполняет роль имени (идентификатора) локальной переменной не определенного типа и «произвольного» размера:
; псевдонимы операндов #region
BUFF_STR equ esp - xmmword * 4
; #endregion
2.2. Псевдонимы переменны и регистров.
Для удобства работы с регистрами создаем блок текстовых констант которые по сути будут представлять собой имена переменных неопределенного типа и размером в двойное слово
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
3. Секция данных
Создаем секцию данных. Стоит отметить что самая «лучшая» секция данных это такая секция которая размещена а секции кода, то есть при любой возможности необходимо избегать создания секции данных и размещать их непосредственно в секции кода в аргументах содержащихся непосредственно в инструкциях, к сожалению
Назначение определенных констант будет пояснено ниже в ходе выполнения процедуры.
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
Назначение определенных констант будет пояснено ниже в ходе выполнения процедуры.
4. Секция кода.
4.1. Поиск начала Числовой подстроки.
4.1.1. Пропуск обобщенных пробелов
4.1.1.1. Вход в цикл пропуска обобщенного пробела.
— сравниваем байты регистра
— уменьшаем указатель адреса первого символа в
— увеличиваем указатель адреса первого символа в
Таким образом при начальном входе в цикл обработки указатель текущего символа будет установлен на начало строки, при последующих входах в цикл он будет смещаться на длину
ХММ3
самими собой в результате чего все байт ХММ3
принимают значение -1
.— уменьшаем указатель адреса первого символа в
CUR_CHAR
на длину ХММ
регистра.— увеличиваем указатель адреса первого символа в
CUR_CHAR
на длину ХММ
регистра. pcmpeqb xmm3, xmm3
sub CUR_CHAR, xmmword
@@: add CUR_CHAR, xmmword
Таким образом при начальном входе в цикл обработки указатель текущего символа будет установлен на начало строки, при последующих входах в цикл он будет смещаться на длину
ХММ-регистра
, то есть на 15 байт. Такой способ организации начала цикла, при котором инкремент расположен в начале цикла, позволяет значительно упростить выход из цикла сведя его к команде проверки условия и условному переходу. В противном случае при размещении команда инкремента и проверки условий в конце цикла эти инструкции конфликтовали бы в части изменения флагов процессора что избыточно усложнило бы выход из цикла.
4.1.1.2. Проверка строки символов.
— загружаем строку символов в
— сравниваем три копии строки содержащиеся в регистрах с тремя строками размещенными в памяти, равномерно заполненными символами
— складываем полученные результаты в регистр
В результате байты регистра
ХММ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
. Векторное сравнение позволяет многократно повысить скорость сканирования строки не только за счет параллельного сравнения но и за счет исключения множества условных переходов характерных для «классических» способов.
4.1.1.3. Проверка результата и выход из цикла.
— командой
— если флаг переноса
В результате сканирование строки продолжается до тех пор пока не будет найден символ отличный от обобщенного пробела. Скорость сканирования можно дополнительно увеличить если разместить строки равномерно заполненные символами
PTEST
выполняет операцию AND
над байтами ХММ0
и ХММ3
и в случае если все байты результата установлены в -1
устанавливаем флаг переноса CF=1
.— если флаг переноса
CF=1
то следовательно в сканируемой строке отсутствуют символы отличные от обобщенного пробела и необходимо вернуться в начало цикла. ptest xmm0, xmm3
jc @b ; повторный пропуск обобщенного пробела
; #endregion
В результате сканирование строки продолжается до тех пор пока не будет найден символ отличный от обобщенного пробела. Скорость сканирования можно дополнительно увеличить если разместить строки равномерно заполненные символами
пробел
/табуляция
/возврат каретки
в старших регистрах SIMD
, но в соответствии с соглашением вызова х64
это потребует предварительно сохранить их значение в память, а при выходе из функции восстановить, что учитывая ожидаемое время сканирования в один проход будет не оправданно.
4.1.2. Позиция первого символа не равного обобщенному пробелу.
-копируем старшие биты всех байтов регистра
— инвертируем
— сканируем биты регистра
— добавляем значение
ХММ0
в EAX
, теперь все биты соответствующие символам обобщенного пробела установлены в значение 1
.— инвертируем
EAX
, теперь биты соответствующие символам не равным обобщенному пробелу установлен в значение 1
.— сканируем биты регистра
EAX
от младшего к старшему в поиске первого бита установлено в значение 1
, и результат равный номеру бита, помещаем в этот же регистр.— добавляем значение
EAX
к CUR_CHAR
и получаем указатель на первый символ отличный от обобщенного пробела. ; Позиция первого символа не равного обобщенному пробелу #region
pmovmskb eax, xmm0
not eax
bsf eax, eax
add CUR_CHAR, eax
; #endregion
4.1.3. Проверка на сочетание символов новой строки.
— устанавливаем флаг нуля
— устанавливаем младший байт регистра
— складываем значение
Таким образом если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов
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
Таким образом если сочетание двух первых символов следующих после символа обобщенного пробела равно сочетанию символов
новая строка
то второй символ после обобщённого пробела будет проигнорирован, в противном случае он будет подвергнут дальнейшему анализу.
4.1.4. Тест обрыва строки.
— копируем первый символ отличный от обобщенного пробела в регистр
— устанавливаем флаг нуля
— если флаг нуля
EAX
одновременно расширяя его до двойного слова.— устанавливаем флаг нуля
ZF=1
если значение EAX
равно 0
.— если флаг нуля
ZF=1
то следовательно имеет место обрыв строки и необходимо выйти из процедуры вернув код ошибки: ; тест обрыва строки #region
movzx eax, byte ptr[CUR_CHAR]
test al, al
jz ErrorExit ; обрыв строки
; #endregion
4.2. Проверка знака Числа.
4.2.1. Проверка символа Плюс.
— сравниваем регистр
— устанавливаем
— добавляем значение
В результате если текущий символ равен символу
AL
с символом плюс
.— устанавливаем
AL
в значение 1
если AL
равен символу плюс
и 0
при любом другом значении символа.— добавляем значение
EAX
к CUR_CHAR
. ; Проверка символа минус/плюс #region
cmp al, '+'
setz al
add CUR_CHAR, eax
В результате если текущий символ равен символу
плюс
то позиция текущего символа будет смещена на следующий символ, во всех остальных случаях символ будет подвергнут повторному анализу.
4.2.2. Проверка символа минус.
— сравниваем текущий символ с символом
— устанавливаем значение
— добавляем значение
— добавляем значение
В результате если текущий символ равен символу
минус
.— устанавливаем значение
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
считается крайне опасным действием чреватым непредсказуемыми ошибками, но я практикую «агрессивный» подход и считаю что не бывает «плохого» или «хорошего» кода, бывают хорошие и плохи программисты, хорошие пишут так как будто никаких правил нет вообще но результат при этом такой как будто они соблюдают их все, а плохие они просто плохие.
4.3. Сканирование символов Числовой строки.
4.3.1. Сканирование старшей части Числовой строки.
4.3.1.1. Проверка первого условия.
— загружаем старшую часть Числовой строки, со смешением на 16 символов от начала Числовой строки, в
— копируем старшую часть Числовой строки в
— сравниваем регистр
— копируем регистр
В результат получаем две копии строки в регистрах
ХММ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
.
4.3.1.2. Проверка второго условия.
— сравниваем регистр
— командой
В результате все байты регистра
ХММ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
.
4.3.1.3. Проверка третьего условия.
— сравниваем регистр
— командой
В результате все байты регистра
ХММ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
.
4.3.1.4. Сохранение старших частей хеша строки.
— копируем старшие биты байтов регистра
— копируем старшие биты байтов регистра
В результате младшие 16 бит регистра
ХММ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
.
4.3.2. Сканирование младшей части Числовой строки.
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
4.3.3. Объединение старших и младших Хешей строки.
— копируем старшие биты байтов регистра
— сдвигаем младшие 16 бит
— складываем значение
— копируем старшие биты байтов регистра
— сдвигаем младшие 16 бит
— складываем значение
В результате
ХММ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
, при этом номер бита соответствуют номерам символов от начала строки начиная с нуля.
4.4. Обработка целой части Числа.
4.4.1. Проверка первого символа.
— сканируем
— если флаг нуля
— устанавливаем флаг нуля
— если флаг нуля
В результат проверяем содержит ли Числовой строки хотя бы один символ
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
.
4.4.2. Поиск символа точки разделяющий целую и дробную части Числа.
— инвертируем значение
— сканируем
— если флаг нуля
— сравниваем символ отличный от
— если флаг нуля
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
4.4.3. Сохранение значащей части Числа.
— копируем
— сканируем
— сохраняем в память строку из четырех нулей
— сохраняем в регистр
— сохраняем в память старшую часть строки символов по адресу указанному в
— сохраняем в регистр
— сохраняем в память младшую часть строки символов начиная с первого символа
В результате сохраняем в память строку из 32 символов начиная с первого символа
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
. При этом указанная строка может содержать символ точки и иные символы не относящиеся к цифрам.
4.5. Обработка дробной части Числа.
4.5.1. Загрузка дробной части Числовой строки.
— загружаем старшую часть Числовой строку, следующую сразу после точки, на которую указывает
— загружаем младшую часть Числовой строку, следующую сразу после точки, на которую указывает
DOT_CHAR
в регистр ХММ0
.— загружаем младшую часть Числовой строку, следующую сразу после точки, на которую указывает
DOT_CHAR
со смещение 16 байт от начала Числовой строки в регистр ХММ1
. ; загрузка дробной части Числовой строки #region
movdqu xmm0,[CUR_CHAR + DOT_CHAR + byte]
movdqu xmm1,[CUR_CHAR + DOT_CHAR + byte + xmmword]
; #endregion
4.5.2. Поиск конца дробной части Числа.
— сбрасываем в
— сканируем
— если флаг нуля
В результате в
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
находиться указатель на первый символ экспоненты или окончание Числа относительно начала Числа.
4.5.3. Количество значащих символов Числа.
4.5.3.1. Проверка наличия значащих цифр.
— сравниваем
— копируем
В результате если и целая и дробная часть Числа состоят из одних нулей а первый символ
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
то есть указателя на первый символ экспоненты или окончания числа.
4.5.3.2. Подсчет количества значащих символов Числа.
— сравниваем
— копируем в
— вычитаем из
В результате в
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
содержится значение количества цифр Числа начиная с первой значащей цифры без учета символа точки, то есть исключительно количество символов соответствующих цифрам, символ точки в подсчете не учитывается даже если число пересекает точку.
4.5.4. Сохранение дробной части Числа.
— вычитаем из
— помещаем в
— Если флаг знака
— сохраняем в память старшую часть строки следующей сразу за символом
— сохраняем в памяти младшую часть строки следующей сразу за символом
В результате если число имеет целую часть то в
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
байтам. Таким образом таким образом если у числа есть целая и дробная часть они будут склеены в единую строку с удалением символа «точки» между ними, если число имеет только целую или только дробную часть, то строка символов начинающаяся после точки будет сохранена за пределами сканируемой строки и таким образом проигнорирована.
4.5.5. Зануление недостающих символов Числовой строки.
— загружаем в
— сохраняем в память строку символов
— сохраняем в память строку символов
— помещаем в
В результат все «мусорные» символы Числовой строки, находящиеся после последнего символа
ХММ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
будет помещена удвоенная сумма знаком между символом точки
и первой значащей цифрой
.
4.6. Проверка корректного окончания Числовой строки.
4.6.1. Размножение окончания дробной части Числовой строки.
— обнуляем регистр
— загружаем в младшее двойное слова регистра
— копируем два символа на которые указывает
— копируем два символа на которые указывает
— копируем
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
4.6.2. Проверка окончания дробной части Числовой строки.
— сравниваем слова регистра
— сравниваем слова регистра
— складываем значение
— сравниваем байты регистра
— командой
— если флаг нуля ZF=0 то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой.
ХММ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
4.6.3. Проверка окончания строки нулем.
— копируем в
— устанавливаем флаг нуля
— если флаг нуля
EDX
два символа начиная с символа который следует за последним символом дробной части Числовой строки и на который указывает END_FRAC
.— устанавливаем флаг нуля
ZF=1
если младший байт регистра EDX
равен 0
.— если флаг нуля
ZF=1[/INLINE то значит число не имеет записи о значении экспоненты и необходимо миновать участок кода связанный с ее дешифровкой.
movzx edx, word ptr[CUR_CHAR + END_FRAC]
test dl, dl
jz @f
4.7. Обработка экспоненты.
4.7.1. Проверка символа экспоненты.
— сбрасываем бит номер
— устанавливаем флаг нуля
— если флаг нуля
5
в регистре EDX
в результате чего если в регистре содержатся символы строчных букв они будут преобразованы в прописные.— устанавливаем флаг нуля
ZF=0
если значение младшего байт регистра EDX
НЕ равно значению символа Е
.— если флаг нуля
ZF=0
то значит числовая строка содержит критическую ошибку в оформлении и необходимо выйти из процедуры вернув код ошибки. ; проверка символа экспоненты #region
btr edx, 5
cmp dl,'E'
jnz ErrorExit
; #endregion
4.7.2. Проверка знака экспоненты
4.7.2.1. Проверка наличия знака экспоненты.
— сбрасываем в
— увеличиваем значение
— сбрасываем в
— если флаг переноса
— складываем значение указателя на текущий символ экспоненты
Таким образом если числовая строка не содержит знака экспоненты то для обработки будет принудительно загружен символ
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
Таким образом если числовая строка не содержит знака экспоненты то для обработки будет принудительно загружен символ
плюс
при этом позиция текущего символа останется на месте. В случае наличия наличия знака экспоненты указатель на текущий символ будет перемещен на следующий символ после символа знака.
4.7.2.2. Проверка знака Экспоненты.
— устанавливаем флаг нуля
— устанавливаем регистр
— устанавливаем флаг нуля
— устанавливаем регистра
— копируем значение бита номер
— складываем
— устанавливаем флаг нуля
— если флаг нуля
Таким образом информация о знаке экспоненты сохраняется в нулевом бите
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
изначально хранит удвоенное значение количество символов между символом точки
и первым символом значащего числа
то его нулевой бит всегда имеет нулевое значение и загрузка в него символа знака экспоненты не исказит значение.
4.7.3. Позиция первого не нулевого символа экспоненты.
— копируем значение
— обнуляем регистр
— устанавливаем в
— складываем значение регистра
— инвертируем значение
—
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
4.7.4. Проверка окончания строки Экспоненты.
; проверка окончания строки экспоненты #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
4.8. Вычисление чего-то зачем-то
; #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
4.9. Вычисление экспоненты и младшей части Числа
; вычисление экспоненты и младшей части Числа #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
4.10. Вычисление множителя
; вычисление множителя #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
4.11. Вычисление целой части Числа.
; вычисление целой части Числа #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
4.12. Вычисление числа.
; вычисление числа #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
4.13. Выход из процедуры.
ret
4.14. ErrorExit
ErrorExit: ; аварийный выход #region
mov ecx, -1
pcmpeqb xmm1, xmm1
psllq xmm1, 52 + 1
psrlq xmm1, 1
ret
; #endregion