Има ли някакъв начин да получите правилно закръгляване с инструкцията i387 fsqrt?

Има ли някакъв начин да получите правилно закръгляване с инструкцията i387 fsqrt?...

...освен промяната на режима на прецизност в контролната дума на x87 - знам, че това е възможно, но не е разумно решение, защото има неприятни проблеми от типа на повторно влизане, при които режимът на прецизност ще бъде грешен, ако sqrt операцията е прекъсната.

Проблемът, с който се занимавам, е следният: операционният код x87 fsqrt изпълнява правилно закръглена (съгласно IEEE 754) операция за извличане на квадратен корен в точността на fpu регистрите, която ще приема, че е разширена (80-битова) точност. Искам обаче да го използвам за прилагане на ефективни функции за квадратен корен с единична и двойна точност с правилно закръглени резултати (съгласно текущия режим на закръгляване). Тъй като резултатът е с прекомерна прецизност, втората стъпка на преобразуване на резултата в кръгове с единична или двойна точност отново, като е възможно да остане неправилно закръглен резултат.

С някои операции е възможно да се заобиколи това с пристрастия. Например, мога да избегна прекомерната прецизност в резултатите от събирането, като добавя отклонение под формата на степен на две, което принуждава 52-те значими бита на стойност с двойна точност в последните 52 бита на 63-битовата мантиса с разширена точност . Но не виждам никакъв очевиден начин да направя такъв трик с корен квадратен.

Някакви умни идеи?

(Също маркиран с C, защото предвиденото приложение е изпълнение на функциите C sqrt и sqrtf.)


person R.. GitHub STOP HELPING ICE    schedule 13.03.2012    source източник
comment
От любопитство: Има ли причина да не можете да използвате математиката SSE2 тук?   -  person    schedule 13.03.2012
comment
Тъй като целта са всички x86 машини, а не след Pentium-2 или каквото и да било.   -  person R.. GitHub STOP HELPING ICE    schedule 13.03.2012
comment
Съхраняването му в 4 или 8 байта памет не прави ли закръгляването? Или това е твърде много режийно?   -  person Mysticial    schedule 13.03.2012
comment
Това изпълнява втора стъпка на закръгляване. Да предположим, че ви моля да закръглите 1,49 до цяло число. Закръгляването му като една стъпка дава 1. Първо закръглянето му до едно място след десетичната запетая дава 1,5, след това закръглянето му до цяло число дава 2. По същия начин fsqrt извършва едно закръгляване (тъй като точната стойност на квадратния корен почти никога не може да бъде представена) и преобразуването му от 80-битова разширена точност в правилния тип извършва друго закръгляване.   -  person R.. GitHub STOP HELPING ICE    schedule 13.03.2012
comment
О, какво имаш предвид. Изкушавам се да мисля, че математическите свойства на квадратния корен ще забранят подобни крайни случаи изобщо да се появят. Но това е малко извън моята област на експертиза.   -  person Mysticial    schedule 13.03.2012
comment
Не се случва често, но има достатъчно много дубли, които се случват от време на време. Точният критерий се оказва нещо като битове 52-64 от точния резултат, изглеждащ като 101000...00, последван от ненулева опашка някъде след края на 64-битовата мантиса с разширена точност. Човек може би може да работи назад и да изброи случаите, но мисля, че са твърде много, за да ги изброя в специални случаи.   -  person R.. GitHub STOP HELPING ICE    schedule 13.03.2012


Отговори (3)


Първо, нека премахнем очевидното от пътя: трябва да използвате SSE вместо x87. Инструкциите SSE sqrtss и sqrtsd правят точно това, което искате, поддържат се във всички модерни x86 системи и са значително по-бързи.

Сега, ако настоявате да използвате x87, ще започна с добрата новина: не е необходимо да правите нищо за float. Имате нужда от 2p + 2 бита, за да изчислите правилно закръглен квадратен корен в p-битов формат с плаваща запетая. Тъй като 80 > 2*24 + 2, допълнителното закръгляване до единична точност винаги ще закръгля правилно и имате правилно закръглен квадратен корен.

Сега лошата новина: 80 < 2*53 + 2, така че няма такъв късмет за двойна точност. Мога да предложа няколко решения; ето един хубав лесен от главата ми.

  1. нека y = round_to_double(x87_square_root(x));
  2. използвайте продукт на Dekker (head-tail), за да изчислите a и b така, че y*y = a + b точно.
  3. изчислете остатъка r = x - a - b.
  4. if (r == 0) return y
  5. if (r > 0), нека y1 = y + 1 ulp и изчислете a1, b1 s.t. y1*y1 = a1 + b1. Сравнете r1 = x - a1 - b1 с r и върнете или y, или y1, в зависимост от това кой има по-малък остатък (или този с нулев бит от нисък ред, ако остатъците са равни по големина).
  6. if (r < 0), направи същото за y1 = y - 1 ulp.

Тази процедура обработва само режима на закръгляване по подразбиране; обаче, в режимите на насочено закръгляване, простото закръгляване до целевия формат прави правилното нещо.

person Stephen Canon    schedule 13.03.2012
comment
+1 Единственият път, когато двойното закръгляне може да се обърка е, ако първото закръгляне е нагоре. Принудителното съкращаване ще се отърве от този проблем. - person Mysticial; 13.03.2012
comment
@Mysticial: не е вярно, че двойното закръгляване е проблем само ако първото закръгляване е нагоре. Да разгледаме стойност от формата ...0 100...00 0...1 (където интервалите означават кръглите точки). Ако закръглим директно до точката на първия кръг, закръгляме до ...1. Въпреки това, ако първо закръглим до втората точка на закръгляване, закръгляме надолу до ...0 100...00; закръгляването отново в точката на първия кръг закръгля надолу до ...0. - person Stephen Canon; 13.03.2012
comment
@Mysticial: тези неща са доста фини, честно казано. - person Stephen Canon; 13.03.2012
comment
Наистина, със закръгляването наполовина нагоре е много по-просто. За съжаление в реалния свят се нуждаем от четно към кръгло, за да избегнем прокрадването на грозни пристрастия. - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
@StephenCanon: Изглежда, че вашият алгоритъм има очевидна възможност да спре на стъпка 1, ако битове 53-64 уникално определят закръгляването, без значение какви са били следващите загубени битове. Само ъгловите кутии трябва да изискват допълнителна работа. - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
И.. имате ли справка за стъпка 2? Продуктът на Dekker не показва подходящи попадения. - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
@R..: разбира се, можеш да излезеш по-рано. Опитвах се да го запазя възможно най-прост =). Продуктът Dekker е трик, дължащ се на T.J. Dekker за изчисляване на a+b = x*y точно с плаваща запетая. Той беше публикуван в Техника с плаваща запетая за разширяване на наличната точност (1971 г.), но можете да намерите алгоритъма подробно във всеки текст за изпълнение на плаваща запетая. Знам, че го има и в ръководството за crlibm, което можете лесно да изтеглите. - person Stephen Canon; 13.03.2012
comment
(Продуктът на Dekker се нарича Mul22 в crlibm, между другото) - person Stephen Canon; 13.03.2012
comment
Всъщност мисля, че е Mul12Cond, но благодаря за справката; Успях да го намеря с това. След небрежен преглед на алгоритъма изглежда, че това отговаря напълно на въпроса, така че се приема! - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
Прав ли съм в разсъжденията си, че единственото време, когато трябва да направим нещо след стъпка 1, е когато последните 11 бита от резултата с разширена точност са 10000000000? - person R.. GitHub STOP HELPING ICE; 15.03.2012
comment
@Stephen: Мисля, че този алгоритъм може да се подобри много, ако работите с незакръгления до двоен резултат с разширена точност като y, а не със закръглената стойност. Струва ми се, че просто трябва да определите дали y е по-малко или по-голямо от действителната стойност на sqrt(x) и след това (съответно) да го увеличите или намалите с 1ulp (разширена точност), преди да закръглите до двойна точност. (Имайте предвид, че това предполага, че сте в случая, когато стойността на разширената точност на y завършва на 10000000000.) - person R.. GitHub STOP HELPING ICE; 15.03.2012
comment
@Stephen: Имайки това предвид, условието C1 на думата за състояние на i387 е 1, ако неточният резултат е закръглен нагоре, и 0, ако е закръглен надолу. - person R.. GitHub STOP HELPING ICE; 15.03.2012
comment
@R..: Да, има много начини да го подобрите (никой от тях не е толкова добър, колкото простото използване на sqrtsd). Исках да дам такъв, който е лесен за числено обяснение и не зависи от неясни характеристики на процесора или много манипулиране на битов модел - не това, което аз лично бих направил, ако внедрявам библиотека. Със сигурност можете да го направите точно както описвате, въпреки че бих отбелязал, че всъщност трябва да измервате ефективността на разклоняването на C1; това е точно нещо, което някога може да е било бързо, но сега въвежда известна архитектурна опасност. - person Stephen Canon; 15.03.2012

Добре, мисля, че имам по-добро решение:

  1. Изчислете y=sqrt(x) с разширена точност (fsqrt).
  2. Ако последните 11 бита не са 0x400, просто преобразувайте в двойна точност и върнете.
  3. Добавете 0x100-(fpu_status_word&0x200) към малката дума на представянето с разширена точност.
  4. Преобразуване в двойна точност и връщане.

Стъпка 3 се основава на факта, че битът C1 (0x200) на думата за състояние е 1, ако и само ако резултатът от fsqrt е закръглен нагоре. Това е валидно, защото поради теста в стъпка 2, x не е перфектен квадрат; ако беше перфектен квадрат, y нямаше да има битове над двойна точност.

Може да е по-бързо да изпълните стъпка 3 с условна работа с плаваща запетая, вместо да работите върху битовото представяне и презареждане.

Ето кода (изглежда, че работи във всички случаи):

sqrt:
    fldl 4(%esp)
    fsqrt
    fstsw %ax
    sub $12,%esp
    fld %st(0)
    fstpt (%esp)
    mov (%esp),%ecx
    and $0x7ff,%ecx
    cmp $0x400,%ecx
    jnz 1f
    and $0x200,%eax
    sub $0x100,%eax
    sub %eax,(%esp)
    fstp %st(0)
    fldt (%esp)
1:  add $12,%esp
    fstpl 4(%esp)
    fldl 4(%esp)
    ret
person R.. GitHub STOP HELPING ICE    schedule 15.03.2012
comment
Този подход изглежда здрав при повърхностна проверка (и със сигурност е по-близък до това, което бих избрал при прилагането на библиотека). Може да го стартирате върху тестовите вектори на Jerome Coonen за допълнителна точка от данни. Защо fld + fstp вместо fst? - person Stephen Canon; 15.03.2012
comment
Доколкото знам, няма версия с разширена точност на fst, а само fstp. - person R.. GitHub STOP HELPING ICE; 15.03.2012

Може да не е това, което искате, тъй като не се възползва от инструкцията 387 fsqrt, но има изненадващо ефективна sqrtf(float) в glibc, реализиран с 32-битова целочислена аритметика. Той дори обработва NaNs, Infs, subnormals правилно - може да е възможно да се елиминират някои от тези проверки с истински x87 инструкции / FP флагове за контролна дума. виж: glibc-2.14/sysdeps/ieee754/flt-32/e_sqrtf.c

Кодът dbl-64/e_sqrt.c не е толкова приятелски настроен. Трудно е да се каже какви предположения се правят с един поглед. Любопитно е, че i386 sqrt[f|l] реализациите на библиотеката просто извикват fsqrt, но зареждат стойността по различен начин. flds за SP, fldl за DP.

person Brett Hale    schedule 13.03.2012
comment
Ще погледна целочисления код. Чудя се дали има подобен подход за двойна точност.. - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
@R.., подозирах, че може да е така. Какви са границите на грешката за IEEE-754 sqrt? 1/2 (ulp) ли е? Режимът на закръгляване засяга ли вътрешното изчисление или само върнатата стойност? - person Brett Hale; 13.03.2012
comment
Резултатът просто трябва да бъде правилно закръглен в текущия режим на закръгляване. Трансценденталните функции са по-малко строги; не е необходимо те да бъдат правилно закръглени, стига върнатият резултат да е правилен в рамките на 1ulp. - person R.. GitHub STOP HELPING ICE; 13.03.2012