Уважаемые программисты! Это снова я и снова с вопросом про десятичную арифметику в FPU. Вот такая процедура: Код (Text): proc TenToX ;10^X=2^P, ;где P=X*lb10 ;P=I+F ;I=INT(P) ;Чтобы F было положительным, устанавливается способ округления ;к минус бесконечности ;для функции F2XM1 показатель должен быть меньше 0.5 ;поэтому F делится пополам ;получаем в ST(0) число X ;возвращаем в ST(0) 10^X ;--------------------------------------- ;команда | ST(0) | ST(1)| ;--------------------------------------- fldl2t; lb10 X fmulp st1,st0; P fstcw [ConW] fwait btr [ConW],11 bts [ConW],10 fldcw [ConW] fld st; P P frndint; I P fxch st1; P I fsub st,st1; F I fidiv [two]; F/2 I f2xm1; 2^(F/2)-1 I fiadd [one]; 2^(F/2) I fmul st,st; 2^F I fscale; 2^F*2^I=10^X I fxch st1; I 10^X fcomp; 10^X ret endp По формулам эту процедуру писал, вроде всё правильно, и она работает, но я никак не подберу параметры округления. Знаю, что за это отвечает поле RC - 10 и 11 биты контрольного слова сопра. Я подбирал все комбинации из этих бит, но - при показателе степени больше 2-х - хрень получается, т.е. 999.(9) Не подскажете, что делать?
Adrax Ты все велосипеды изобретаешь Слышал звон, да не знаешь где он ) И то и другое неверно, т.к. операнд F2XM1 может\должен лежать в диапазоне от -1 до +1. Поэтому в функции Pow способ округления при frndint используется к ближайшему целому RC = 00b = по умолчанию после finit (см.например тут) А 999.(9) может получаться как "само по себе" за счет неточности промежкточных вычислений, так и за счет того, что ты изменил RC, а назад не вернул. Когда нужно округление вниз, то перед frndint изменяют RC, а после возвращают назад, т.к. округление влияет на все (промежуточные) операции, при которых получается "неточный" результат (не умещающийся в 64-битную мантиссу) PS: А ты подумал, где тебе может пригодиться возведение 10 в дробную степень Обычно используют или универсальную, но медленную ф-ю Pow = x^y (нужны доп.проверки х и y), или более быстрое возведение x в целую степень IntPow или супербыструю табличную Pow10 для возведения 10 в целую степень
leo Блин, ты один мне советы даёшь... Процедуру эту я по формулам писал из одной статьи - в достоверности не усомнился, того и сделал, как там... Просто не хотелось мне табличным методом, хотелось в процедуре 10^x, думал, заодно, в отдельную инклуду вынесу, для десятичной арифметики - на будущее... А IntPow и Pow10 - это, пардон, что? Масмовские макросы? Где посмотреть?
Не масмовские и не макросы Pow10 - это функция возведения 10 в целую степень, есть и в Cи, и в паскале\дельфи (_Pow10 или FPower10 в system.pas). Использует таблично-вычислительный метод (малые степени берутся прямо из таблицы, а большие комбинируются из малых степеней и степеней, равных степени 2, т.е. 10^16, 10^32 и т.д.) IntPower в явном виде присутствует в дельфи (math.pas), а неявно используется во всех стандартных Pow\Power при целых степенях, т.к. работает значительно быстрее. Используется стандартный прием shift-and-add, основанный на двоичном представлении показателя степени n. Для каждого значащего бита n производится умножние x=x*x и если бит = 1, то результат умножается на этот x - в результате тормозные трансцендентые операции заменяются m+k умножениями, где m - число значащих бит n (до старшей единицы), а k - число единичных бит Вот реализация IntPower из math.pas дельфи Код (Text): function IntPower(Base: Extended; Exponent: Integer): Extended; asm //register call -> eax = Exponent, Base - в стеке mov ecx, eax cdq fld1 { Result := 1 } xor eax, edx sub eax, edx { eax := Abs(Exponent) } jz @@3 fld Base jmp @@2 @@1: fmul ST, ST { X := Base * Base } @@2: shr eax,1 jnc @@1 fmul ST(1),ST { Result := Result * X } jnz @@1 fstp st { pop X from FPU stack } cmp ecx, 0 jge @@3 fld1 fdivrp { Result := 1 / Result } @@3: fwait end;
leo Любопытный код, только так имхо лучше будет: Код (Text): mov eax, [Exponent] test eax, eax fld1 ; { Result := 1 } jz @@Exit ; Base^0 = 1 fld [Base] jns @@2 fdivr ST, ST(1) ; { Base := 1 / Base } neg eax jmp @@2 @@1: fmul ST, ST ; { X := Base * Base } @@2: shr eax, 1 jnc @@1 fmul ST(1), ST ; { Result := Result * X } jnz @@1 fstp ST ; { pop X from FPU stack } @@Exit: ; Результат возвращается в ST Я правильно понимаю, что так fdivr распараллелится с neg, jmp, shr, jnc и ждать её придётся только fmul ?
Пробую сравнить свой вариант возведения в степень с Дельфийским от leo. Отличие в скоростях небольшое и не стабильное то один вариант обгоняет то другой , более менее понятна заметная зависимость скорости от выравнивания, хотя почему-то выравнивание точки входа ускоряет, а выравнивание основного цикла замедляет работу ?. Но вот почему на PII скорость зависит от количества переводов строк в wsprintf, которая вроде как к измерению скорости отношения не имеет - совершенно непонятно (на PM этот эффект не наблюдается).
Во-первых, при настроенных переходах jnc заметной разницы быть не должно, т.к. все перчисленные операции сами по себе занимают пару тактов да еще могут параллелятся с fld [Base]. А вот в случае ошибки предсказания первого jnc (при переходе от нечетного Exponent к четному или наоборот) твой код может получить доп.штраф, т.к. задержка непредсказанного перехода = длине конвеера + время ожидания отставки неверного jnc - а утебя jnc не может уйти в отставку пока не выполнится fdivr. Это можно проверить, например так: берем тест из 10 итераций c Exponent = -15 и на 7й или 8й итерации меняем Exponent на -14. В результате на этой итерации происходит сбой предсказания и задержка существенно увеличивается в обоих вариантах, но в твоем она увеличивается заметно больше, чем в дельфийском IntPower Во-вторых, твой вариант может давать бОльшую погрешность в случае, когда Base представляется точно (например 3 или 7), а 1/Base - неточно (1/3 или 1/7). В этом сл. умножения в IntPower выполняются точно и затем один раз делается fdiv, а у тебя за счет умножения неточных 1/Base погрешность будет нарастать. Хотя для реальных применений это видимо не имеет существенного значения Я уже не раз говорил, что для P6 и AMD важно не само по себе выравнивание, а 1) наличие достаточного числа инструкций от метки цикла до конца 16-байтного блока, 2) отсутствие разбивки "критичных" инструкций границами 16-байтных блоков, 3) учет ограничения не более 3-х jcc\jmp в блоке. Поэтому и выравнивание цикла на 16 не всегда дает лучшие результаты, иногда лучше работает align 4 или 8 или вообще произвольное число нопов