Необходимо предотвратить функцию от генерирования внутри нее исключительных ситуаций. Принимаются любые числовые значения, при этом только возможные исключения занимают стек числовых регистров.
Любое значение, посланное в числовой стек, проверяется на существование, тип (NaN или бесконечность) и состояние (денормальное, ноль, знак). Размер строки тестируется при минимальном и максимальном значении. Если вершина регистрового стека пуста или размер строки слишком мал, функция возвращает код ошибки.
Переполнение и отрицательное переполнение при очень больших или очень маленьких числах предотврашаются внутри функции.
Рисунок 20-6. Подпрограмма Перевода Чисел с Плавающей Точкой в ASCII Вид +-------------------------------------------------------------------------+| || || SOURCE || || +1 $title('Перевод числа с плавающей точкой в ASCII вид') || || || name floating_to_ascii || || public floating_to_ascii || extrn get_power_10:near, tos_status:near || ; || ; Эта подпрограмма переводит число с плавающей точкой || ; из вершины стека FPU в строку типа ASCII и отделяет || ; степень 10, масштабируя значение (в двоичном виде). || ; Максимальная длина строки символов регулируется || ; параметром, который должен быть больше 1. || ; Не-нормальные значения, денормальные значения и псевдо || ; -нули переводятся корректно. Однако, не-нормальные || ; величины и псевдо-нули более не поддерживаются || ; процессором i486 (в соответствии со стандартом IEEE) и || ; внутренне не генерируются. Возвращаемое значение || ; указывает сколько двоичных разрядов точности было || ; потеряно в не-нормальных или денормальных значениях. || ; Также указывается и величина мантиссы псевдо-нуля || ; (двоичным порядком). Целые числа меньшие 10**18 || ; переводятся точно, если принимающая строка ASCII || ; символов содержит достаточное количество позиций для || ; цифр этих чисел. В противном случае значение || ; переводиться в научную нотацию. || ; || ; В зависимости от результата подпрограмма выдает || ; следующие значения: || ; || ; 0 перевод выполнен, размер строки определен || ; 1 недопустимые аргументы || ; 2 точный целочисленный перевод, размер строки || ; определен || ; 3 неопределенность || ; 4 + NaN (не-число) || ; 5 - NaN || ; 6 + Бесконечность || ; 7 - Бесконечность || ; 8 встречен псевдо-ноль, размер строки определен || ; || ; Интерфейс для вызова из PLM-386/486 || ; || ; floating_to_ascii: || ; procedure (number, denormal_ptr, string_ptr, size_ptr, || ; field_size, power_ptr) word external; || ; declare (denormal_ptr, string_ptr, power_ptr, size_ptr) || ; pointer; || ; declare field_size word; || ; string_size based size_ptr word; || ; declare number real; || ; declare denormal integer based denormal_ptr; || ; || || ; declare power integer based power_ptr; || ; end floating_to_ascii; || ; || ; Величина с плавающей точкой должна быть на вершине || ; стека FPU. Эта подпрограмма требует три свободные || ; регистра в стеке и после отработки выталкивает || ; передаваемые значения из стека. Полученная строка || ; будет иметь начальный символ либо "+", либо "-", || ; указывая на знак величины. Затем следуют десятичные || ; цифры в ASCII виде. Числовое значение строки типа || ; ASCII будет равно (ASCII СТРОКА)*10**ПОРЯДОК. Если || ; данное число было нулем, то строка будет содержать || ; только знак и один символ 0. Величина размера строки || ; (string_size) указывает на полную длину строки символов, || ; включая символ знака. Строка (0) всегда будет || ; содержать знак. Возможно, что размер строки будет || ; меньше размера поля. Это бывает при нулях и целых || ; числах. Псевдо-ноль выдает особый код возврата. При || ; денормальных числах указывается степень двух || ; представленного значения. Степень десяти и строка будут || ; такими же, как если бы величина была бы простым нулем. || ; || ; Эта подпрограмма точно выдает десятичные целые до 18 || ; цифр. Целые величины имеют десятичный показатель || ; степени в строке из нулей. При нецелых величинах || ; точность результата заключена в двух последних || ; десятичных цифрах (двойная точность). Для || ; масштабирования величины в диапазоне, приемлемом для || ; данных типа BCD, используются команды возведения в || ; степень. Для перевода используется режим округления, || ; действующий при входе в подпрограмму. || ; || ; Следующие регистры не видны: || ; || ; eax ebx ecx edx esi edi eflags || ; || ; Определение стека. || ; || ebp_save equ dword ptr [ebp] || es_save equ ebp_save + size ebp_save || return_ptr equ es_save + size es_save || power_ptr equ return_ptr + size return_ptr || field_size equ power_ptr + size power_ptr || size_ptr equ field_size + size size_ptr || string_ptr equ size_ptr + size size_ptr || denormal_ptr equ string_ptr + size string_ptr || || parms_size equ size power_ptr + size field_size + || & size size_ptr + size string_ptr + || & size denormal_ptr || ; || ; Определение используемых констант. || ; || BCD_DIGITS equ 18 ; Количество цифр в величине типа BCD || WORD_SIZE equ 4 || BCD_SIZE equ 10 || MINUS equ 1 ; Определение выдаваемых значений || NAN equ 4 ; Выбраные здесь точные величины - || INFINITY equ 6 ; важны. Они должны соответствовать || INDEFINITE equ 3 ; возможным выдаваемым значениям и || PSEUDO_ZERO equ 8 ; тестироваться в том же порядке, как || INVALID equ -2 ; показано в этой программе. || ZERO equ -4 || DENORMAL equ -6 || UNNORMAL equ -8 || NORMAL equ 0 || EXACT equ 2 || ; || ; Определение положения временной области хранения. || ; || power_two equ word ptr [ebp - WORD_SIZE] || bcd_value equ tbyte ptr power_two - BCD_SIZE || bcd-byte equ byte ptr bcd_value || fraction equ bcd_value || || lokal_size equ size power_two + size bcd_value || ; || ; Выделить достаточный объем стека для || ; временных результатов. || ; || stack stackseg (lokal_size+6) ; Выделить пространство стека || ; для локальных данных. || +1 $eject || || code segment public er || extrn power_table:qword || ; || ; Константы, используемые этой функцией. || ; || even ; Оптимизировать до 16 цифр. || const10 dw 10 ; Подрегулировать значение для || ; ; слишком больших BCD. || ; || ; Перевести биты C3, C2, C1 и C0 в || ; значащие флаги и величины, используя || ; процедуру tos_status. || ; || status_table db UNNORMAL, NAN, UNNORMAL+MINUS, || & NAN+MINUS, NORMAL, INFINITY, || & NORMAL+MINUS, INFINITY+MINUS, || & ZERO, INVALID, ZERO+MINUS, INVALID, || & DENORMAL, INVALID, DENORMAL+MINUS, INVALID || floting_to_ascii proc || || call tos_status ; Посмотреть на состояние ST(0) || ; || ; Взять дескриптор из таблицы || ; || movzx eax, status_table[eax] || cmp al,INVALID ; ST(0) пуст? || jne not_empty || ; || ; ST(0) - пуст! Возвращает значение состояния. || ; || ret parms_size || ; || ; Удалить бесконечность из стека и выйти. || ; || found_infinity: || fstp st(0) ; Оставить fstp || jmp short exit_proc || ; || ; Длина строки слишком мала! || ; Выдает код недопустимости. || ; || small_string: || mov al,INVALID || exit_proc: || leave ; Восстановить стек || || pop es || ret parms_size || ; || ; В ST(0) находится NaN или || ; неопределенность. Сохранить значение || ; в памяти и просмотреть дробное поле || ; для того, чтобы отличить || ; неопределенность от обычного NaN. || ; || NAN_or_indefinite: || fstp fraction ; Для проверки - удалить значение || ; из стека. || test al,MINUS ; Посмотреть бит знака. || fwait ; Убедиться в выполнении сохранения. || jz exit_proc ; Если положительный знак, то не || ; может быть неопределенностью. || || mov ebx,0C0000000H ; Подавить верхние 32 разряда дробной || ; части. || || ; Сравнить разряды 63-32 || sub ebx,dword ptr fraction + 4 || || ; Разряды 31-0 должны быть нулями || or ebx,dword ptr fraction || jnz exit_proc || || ; Установить выдаваемое значение на || ; неопределенность || mov al,INDEFINITE || jmp exit_proc || ; || ; Выделить место в стеке для локальных переменных || ; и установить параметр адресации. || ; || not_empty: || push es ; Сохранить рабочий регистр || enter local_size,0 ; Установить адресацию стека || || ; Проверить, достаточно ли места в строке || mov ecx,field_size || cmp ecx,2 || jl small_string || || dec ecx ; Установить символ знака || || ; Посмотреть, может быть строка слишком большая для типа BCD || cmp ecx,BCD_DIGITS || jbe size_ok || || ; Иначе установить максимальный размер строки || mov ecx,BCD_DIGITS || size_ok: || cmp al,INFINITY ; Может быть бесконечность? || || ; Возвратить значение состояния для + или - бесконечности || jge found_infinity || || cmp al,NAN ; Можеть быть NaN или || jge NAN_or_indefinite ; неопределенность || || ; || ; Установить выдаваемое значение по умолчанию || ; и проверить, нормализовано ли число. || ; || fabs ; Использовать только положительные значения || ; || ; Бит знака в регистре AL содержит истинный знак величины. || ; || xor edx,edx ; Подготовить константу 0 || mov edi,denormal_ptr ; Обнулить счетчик денормальных чисел || mov [edi],dx || mov ebx,power_ptr ; Обнулить значение степени десяти || mov [ebx],dx || mov dl,al || and dl,1 || add dl,EXACT || cmp al,ZERO ; Проверить на ноль || jae convert_integer ; Перейти программу возведения в || ; степень, если значение равно нулю. || || fstp fraction || fwait || mov al,bcd_byte + 7 || or byte ptr bcd_byte +7,80h || fld fraction || fxtract || test al,80h || jnz normal_value || || fld1 || fsub || ftst || fstsw ax || sahf || jnz set_unnormal_count || ; || ; Найден псевдо-ноль || ; || fldlg2 ; Оценить степень десяти || add dl,PSEUDO_ZERO - EXACT || fmulp st(2),st || fxch ; Взять степеть десяти || fistp word ptr [ebx] ; Установить степень десяти || jmp convert_integer || || set_unnormal_count: || fxtract ; Взять исходную дробь, || ; и нормализовать. || fxch ; Взять счетчик не-нормальных чисел || fchs || fistp word ptr [edi] ; Установить счетчик || ; не-нормальных чисел || || ; || ; Вычислить десятичную величину вместе с этим числом || ; внутри одного порядка. || ; || ; Всегда при округлении будет присутствовать ошибка || ; из-за потери точности. В результате, мы || ; преднамеренно при вычислении порядка не стали || ; рассматривать LOG10 от значения дроби. Так как || ; дробь всегда больше или равна 1 и меньше двух, то || ; LOG10 от этой дроби не изменяет основной точности || ; функции. Для того, чтобы получить десятичный || ; порядок величины, надо просто умножить степень || ; двух на LOG10(2) и округлить результат с || ; отсечением до целого. || ; || normal_value: || fstp fraction ; Для дальнейшего использования || ; сохранить поле дроби. || fist power_twq ; Сохранить степень двух || fldlg2 ; Взять LOG10(2) || ; Теперь можно безопасно использовать || ; power_two || fmul ; Подготовить LOG10 от порядка числа || fistp word ptr [ebx] ; Здесь может быть применен любой || ; режим округления || ; || ; Проверить, чтобы величина числа || ; интерпретировалась как целое. || ; || ; CX имеет максимально позволенное количество десятичных цифр. || ; || fwait ; Ожидать допустимой степени десяти || ; || ; Возвести десять в степень величины значения || ; || movsx si,word ptr [ebx] || sub esi,ecx ; Подготовить в AX необходимый || ; коэффициент масштабирования. || ja adjust_result ; Перейти, если число не подходит || ; || ; Число между 1 и 10**(field_size - размер поля) || ; Проверить на целочисленность || ; || fild power_two ; Восстановить начальное значение || sub dl,NORMAL-EXACT ; Перевести в точное выдаваемое || ; значение || fld farction || fscale ; Подготовить полную величину, в этой || ; команде она не портится || fst st(1) ; Скопировать значение для сравнения || frndint ; Проверить на целочисленность || fcomp ; Сравнить значения || fstsw ax ; Сохранить состояние || sahf ; C3 = 1, значит это была целое число || || jnz convert_integer || || fstp st(0) ; Удалить нецелочисленное значение || add dl,NORMAL-EXACT ; Восстановить начальнное выдаваемое || ; значение || ; || ; Масштабировать число внутри диапазона, || ; позволенного форматом BCD. Операция || ; масштабирования выдает число внутри одного || ; десятичного порядка величины наибольшего || ; десятичного числа, представимого для данной длины || ; строки. || ; || ; Величина степени десяти для масштабирования || ; находится в регистре SI. || ; || adjust_result: || mov eax,esi ; Подготовить для возведения в || ; степень || mov word ptr [ebx],ax ; Установить начальную величину || ; степени десяти || neg eax ; Вычесть единицу для каждого порядка || ; величины, на который масштабируется || ; значение. || call get_power_10 ; Коэффициент масштабирования || ; представляется как порядок и дробь. || fld fraction ; Взять дробь || fmul ; Комбинировать дроби || mov esi,ecx ; Возвести десять в максимальную || ; степень || shl esi,3 ; Для того, чтобы значение BCD || ; входило в строку || fild power_two ; Комбинировать степень двух || faddp st(2),st || fscale ; Подготовить полное значение, || ; порядок остался не тронутым || fstp st(1) ; Удалить порядок || ; || ; Проверьте установленное значение по таблице || ; точных степеней десяти. Суммарные ошибки оценки || ; величины и степенной функции могут привести к || ; тому, что значение одного порядка величины будет || ; либо слишком маленькое, либо слишком большое для || ; поля типа BCD. Для устранения этой проблемы || ; протестируйте полученное значение - является ли || ; оно слишком большим или слишком маленьким. Затем || ; отрегулируйте его и значение степени десяти. || ; || test_power: || ; || ; Сравните с точной степенью. Используйте следующую || ; степень при уменьшении CX на единицу. || ; || fcom power_table[esi]+type power_table || fstsw ax ; Не надо ждать || sahf ; Если C3=C0=0, то слишком большое || jb test_for-small || fidiv const10 ; Иначе установить значение || and dl,not EXACT ; Удалить флаг того, что число точное || inc word ptr [ebx] ; Установить значение степени десяти || jmp short in_range ; Перевести значение в целое типа BCD || || test_for_small: || fcom power_table[esi] ; Проверить относительный размер || || fstsw ax ; Не ждать || sahf ; Если C0 = 0, то ST(0) больше или || ; равен нижшей границе || jc in_range ; Перевести значение в целое типа BCD || fimul const10 ; Подогнать значение под диапазон || dec word ptr [ebx] ; Подобрать значение степени десяти || in_range: || frndint ; Подготовить целое значение || ; || ; Утверждение: 0 <= TOS <= 999,999,999,999,999,999 || ; Число TOS будет точно представлено || ; 18-ю цифрами в формате BCD. || ; || convert_integer: || fbstp bcd_value ; Сохранить число в формате BCD || ; || ; При сохранении формата BCD установить регистры || ; для перевода в ASCII вид. || ; || mov esi,BCD_SIZE-2 ; Инициализировать значение || ; индекса BCD || mov cx,0f04h ; Установить счетчик сдвига и маску || mov ebx,1 ; Установить начальный размер ASCII || ; поля для знака || mov edi,string_ptr ; Взять адрес начала ASCII строки || mov ax,ds ; Скопировать DS в ES || mov es,ax || cld ; Установить режим автоматического || ; добавления единицы || mov al,'+' ; Очистить поле знака || test dl,MINUS ; Проверить на отрицательное значение || jz positive_result || || mov al,'-' || positive_result: || stosb ; Установить указатель строки на || ; последний знак || and dl,not MINUS ; Выключить бит знака || fwait ; Ожидать окончания команды fbstp || ; || ; Используемые регистры: || ; || ; AH: байт со значением типа BCD || ; AL: значение строки ASCII || ; DX: возвращаемое значение || ; CH: маска BCD = 0fh || ; CL: счетчик сдвига BCD = 4 || ; BX: ширина поля строки ASCII || ; ESI: индекс поля BCD || ; DI: указатель поля строки BCD || ; DS, ES: адрес сегмента строки ASCII || ; || ; Удалить начальные нули из числа. || ; || ; || skip_leading_zeroes: || mov ah,bcd_byte[esi] ; Взять байт BCD || mov al,ah ; Скопировать значение || shr al,cl ; Взять верхнюю по порядку цифру || and al,0fh ; Установить флаг нуля || jnz enter_odd ; Выйти из цикла, если в начале || ; обнаружены не нули || || mov al,ah ; Взять снова байт BCD || and al,0fh ; Взять нижнюю по порядку цифру || jnz enter-even ; Выйти из цикла, если обнаружены || ; ненулевые цифры || || dec esi ; Уменьшить индекс BCD || jns skip_leading_zeroes || ; || ; Вся мантисса состоит из нулей || ; || mov al,'0' ; Установить начальный ноль || stosb || inc ebx ; Увеличить длину строки || jmp short exit_with_value || ; || ; Теперь расширяем строку цифрами от || ; 0 до 9 по одной цифре на байт || ; || digit_loop: || mov ah,bcd_byte[esi] ; Взять байт BCD || mov al,ah || shr al,cl ; Взять верхнюю по порядку цифру || enter_odd: || add al,'0' ; Перевести в ASCII || stosb ; Занести цифру в строку ASCII || mov al,ah ; Взять нижнюю по порядку цифру || and al,0fh || inc ebx ; Увеличить счетчик размера поля || enter-even: || add al,'0' ; Перевести в ASCII || stosb ; Занести цифру в строку ASCII || inc ebx ; Увеличить счетчик размера поля || dec esi ; Перейти к следующему байту BCD || jns digit_loop || ; || ; Перевод закончен. Установить размер || ; строки и остаток || ; || exit_with_value: || mov edi,size_ptr || mov word ptr [edi],bx || mov eax,edx ; Установить выдаваемое значение || jmp exit_proc || || floating_to_ascii endp || code ends || end || || || +1 $title(Вычислить значение 10**AX) || || ; Эта подпрограмма вычисляет значение степени || ; 10**EAX. Точный результат выдается для значений в || ; диапазоне 0 <= EAX < 19. Все регистры прозрачны || ; и значение выдается в TOS как два числа: порядок || ; в ST(1) и дробь в ST(0). Величина порядка может || ; превышать наибольший порядок числа в расширенном || ; вещественном формате. В программе используются || ; три стековых регистра. || ; || name get_power_10 || public get_power_10, power_table || || stack stackseg 8 || || code segment public er || ; || ; Использовать точные значения от 1.0 до 1E18. || ; || even ; Оптимизировать 16-ти битовый доступ || power_table dq 1.0,1e1,1e2,1e3 || || dq 1e4,1e5,1e6,1e7 || || dq 1e8,1e9,1e10,1e11 || || dq 1e12,1e13,1e14,1e15 || || dq 1e16,1e17,1e18 || || get_power_10 proc || cmp eax,18 ; Проверить диапазон 0 <= AX < 19 || ja out_of_range || || fld power_table[eax*8] ; Взять точное значение || fxtract ; Отделить степень и || ; дробную часть || ret ; Оставить fxtract || ; || ; Вычислить значение, используя команду возведения || ; в степень. Используются следующие соотношения: || ; || ; 10**X = 2**(log2(10)*X) || ; 2**(I+F) = 2**I * 2**F || ; || ; Если ST(1) = I и ST(0) = 2**F, то команда fscale || ; выдает 2**(I+F). || ; || out_of_range: || || fldl2t ; TOS = LOG2(10) || enter 4,0 || || ; Сохранить значение степени десяти, P || ; || mov [ebp-4],eax || || ; TOS, X = LOG2(10)*P = LOG2(10**P) || ; || fimul dword ptr [ebp-4] || fld1 ; Установить TOS = -1.0 || fchs || fld st(1) ; Скопировать значение степени по || ; основанию два || frndint ; TOS = I; -бесконечность < I <= X, || ; где I - целое || ; Режим округления не имеет значения || fxch st(2) ; TOS = X, ST(1) = -1.0 || ; ST(2) = I || fsub st,st(2) ; TOS, F = X-I: || ; -1.0 < TOS <= 1.0 || || ; Восстановить начальный режим управления точностью || pop eax || f2xm1 ; TOS = 2**(F) - 1.0 || leave ; Восстановить стек || fsubr ; Подготовить 2**(F) || ret ; Оставить fsubr || || get_power_10 endp || || code ends || end || || +1 $title(Определение содержимого регистра TOS) || ; || ; Эта подпрограмма выдает значение от 0 до 15 в || ; регистр EAX в соответствии с содержанием вершины || ; стека FPU. Все регистры прозрачны, поэтому ошибки || ; исключены. Выдаваемое значение соответствует || ; битам C3, C2, C1 и C0 команды FXAM. || ; || name tos_status || public tos_status || || stack stackseg 6 || || code segment public er || || tos_status proc || fxam ; Взять состояние регистра TOS || fstsw ax ; Взять текущее состояние || mov al,ah ; Положить биты 10-8 в биты 2-0 || and eax,4007h ; Маскировать биты C3, C2, C1 и C0 || shr ah,3 ; Положить бит C3 в бит 11 || or al,ah ; Положить бит C3 в бит 3 || mov ah,0 ; Очистить возвращаемое значение || ret || || tos_status endp || || code ends || end || || |+-------------------------------------------------------------------------+