Округление в FPU

Тема в разделе "WASM.BEGINNERS", создана пользователем Adrax, 8 фев 2007.

  1. Adrax

    Adrax Алексей

    Публикаций:
    0
    Уважаемые программисты! Это снова я и снова с вопросом про десятичную арифметику в FPU. Вот такая процедура:
    Код (Text):
    1. proc TenToX
    2. ;10^X=2^P,
    3. ;где P=X*lb10
    4. ;P=I+F
    5. ;I=INT(P)
    6. ;Чтобы F было положительным,  устанавливается способ округления
    7. ;к минус бесконечности
    8. ;для функции F2XM1  показатель  должен  быть меньше 0.5
    9. ;поэтому F делится пополам
    10. ;получаем в ST(0) число X
    11. ;возвращаем в ST(0) 10^X
    12. ;---------------------------------------
    13. ;команда           | ST(0)      | ST(1)|
    14. ;---------------------------------------
    15. fldl2t;               lb10          X
    16. fmulp st1,st0;          P
    17. fstcw [ConW]
    18. fwait
    19. btr [ConW],11
    20. bts [ConW],10
    21. fldcw [ConW]
    22. fld st;                 P           P
    23. frndint;                I           P
    24. fxch st1;               P           I
    25. fsub st,st1;            F           I
    26. fidiv [two];           F/2          I
    27. f2xm1;            2^(F/2)-1         I
    28. fiadd [one];       2^(F/2)          I
    29. fmul st,st;            2^F          I
    30. fscale;           2^F*2^I=10^X      I
    31. fxch st1;               I          10^X
    32. fcomp;                 10^X
    33. ret
    34. endp
    По формулам эту процедуру писал, вроде всё правильно, и она работает, но я никак не подберу параметры округления. Знаю, что за это отвечает поле RC - 10 и 11 биты контрольного слова сопра. Я подбирал все комбинации из этих бит, но - при показателе степени больше 2-х - хрень получается, т.е. 999.(9)
    Не подскажете, что делать?
     
  2. leo

    leo Active Member

    Публикаций:
    0
    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 в целую степень
     
  3. Adrax

    Adrax Алексей

    Публикаций:
    0
    leo
    Блин, ты один мне советы даёшь... Процедуру эту я по формулам писал из одной статьи - в достоверности не усомнился, того и сделал, как там...
    Просто не хотелось мне табличным методом, хотелось в процедуре 10^x, думал, заодно, в отдельную инклуду вынесу, для десятичной арифметики - на будущее...
    А IntPow и Pow10 - это, пардон, что? Масмовские макросы? Где посмотреть?
     
  4. leo

    leo Active Member

    Публикаций:
    0
    Не масмовские и не макросы ;)
    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):
    1. function IntPower(Base: Extended; Exponent: Integer): Extended;
    2. asm  //register call -> eax = Exponent, Base - в стеке
    3.         mov     ecx, eax
    4.         cdq
    5.         fld1                      { Result := 1 }
    6.         xor     eax, edx
    7.         sub     eax, edx          { eax := Abs(Exponent) }
    8.         jz      @@3
    9.         fld     Base
    10.         jmp     @@2
    11. @@1:    fmul    ST, ST            { X := Base * Base }
    12. @@2:    shr     eax,1
    13.         jnc     @@1
    14.         fmul    ST(1),ST          { Result := Result * X }
    15.         jnz     @@1
    16.         fstp    st                { pop X from FPU stack }
    17.         cmp     ecx, 0
    18.         jge     @@3
    19.         fld1
    20.         fdivrp                    { Result := 1 / Result }
    21. @@3:
    22.         fwait
    23. end;
     
  5. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    leo
    Любопытный код, только так имхо лучше будет:
    Код (Text):
    1.     mov eax, [Exponent]
    2.     test eax, eax
    3.     fld1            ; { Result := 1 }
    4.     jz  @@Exit      ; Base^0 = 1
    5.     fld [Base]
    6.     jns @@2
    7.        fdivr ST, ST(1)  ; { Base := 1 / Base }
    8.        neg eax
    9.        jmp     @@2
    10. @@1:    fmul    ST, ST      ; { X := Base * Base }
    11. @@2:    shr     eax, 1
    12.     jnc     @@1
    13.     fmul    ST(1), ST   ; { Result := Result * X }
    14.     jnz     @@1
    15.     fstp    ST      ; { pop X from FPU stack }
    16. @@Exit:
    17. ; Результат возвращается в ST
    Я правильно понимаю, что так fdivr распараллелится с neg, jmp, shr, jnc и ждать её придётся только fmul ?
     
  6. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Пробую сравнить свой вариант возведения в степень с Дельфийским от leo.
    Отличие в скоростях небольшое и не стабильное то один вариант обгоняет то другой :), более менее понятна заметная зависимость скорости от выравнивания, хотя почему-то выравнивание точки входа ускоряет, а выравнивание основного цикла замедляет работу ?:dntknw:. Но вот почему на PII скорость зависит от количества переводов строк в wsprintf, которая вроде как к измерению скорости отношения не имеет - совершенно непонятно (на PM этот эффект не наблюдается).
     
  7. leo

    leo Active Member

    Публикаций:
    0
    Во-первых, при настроенных переходах 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 или вообще произвольное число нопов