Преобразование строки символов в число двойной точности (double)

от автора

Преобразование строки символов в число двойной точности на 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. Сохранение старших частей хеша строки.

— копируем старшие биты байтов регистра ХММ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. Объединение старших и младших Хешей строки.

— копируем старшие биты байтов регистра ХММ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. Сохранение значащей части Числа.

— копируем 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. Проверка окончания дробной части Числовой строки.

— сравниваем слова регистра ХММ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 

ссылка на оригинал статьи https://habr.com/ru/post/549270/


Комментарии

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

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