Как се прилага Arctan?

Много от реализациите на библиотеката се простират дълбоко до инструкцията FPATAN за всички дъгови функции. Как се прилага FPATAN? Ако приемем, че имаме 1 битов знак, M бита мантиса и N бита експонента, какъв е алгоритъмът за получаване на аркутангенса на това число? Трябва да има такъв алгоритъм, тъй като FPU го прави.


person Plamen Dragiyski    schedule 13.04.2014    source източник


Отговори (3)


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

Ето реализация на atan2: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/ieee754/dbl-64/e_atan2.c;h=a287ca6656b210c77367eec3c46d72f18476d61d;hb=HEAD

Редактиране: Всъщност намерих това: http://www.netlib.org/fdlibm/e_atan2.c, което е много по-лесно за следване, но вероятно по-бавно поради това (?).

FPU прави всичко това в някои схеми, така че процесорът не трябва да върши цялата тази работа.

person typ1232    schedule 13.04.2014
comment
Благодаря много. На първия линк включва и mpatan.h и mpatan.c, където има имплементация на atan - точно това, което търсих. - person Plamen Dragiyski; 14.04.2014
comment
не всички FPU правят това в хардуера. Може да има архитектура, която няма тригонометрични инструкции. SSE не поддържа и тригонометрия, така че MSVC 2013 трябва да внедри софтуерен, когато векторизира код - person phuclv; 16.04.2014
comment
Инструкцията FPATAN в процесорите x86 обикновено се изпълнява чрез микрокод, тоест малка програма, съхранена във вътрешен ROM вътре в процесора. Въпреки че такива програми могат да използват специализирани операции, които не са налични във видимата ISA, обикновено не е включена специална схема. - person njuffa; 16.04.2014
comment
втората реализация на atan2 е много по-кратка, защото използва atan. - person lrineau; 16.04.2014

Реализациите на инструкциите FPATAN в процесори x86 обикновено са патентовани. За да изчислят arctan или други (обратни) тригонометрични функции, общите алгоритми следват процес от три стъпки:

  1. намаляване на аргумента за картографиране на пълния входен домейн към тесен интервал
  2. изчисляване на основната апроксимация на тесния интервал (първичен апроксимационен интервал)
  3. разширяване на междинния резултат въз основа на намаляването на аргумента за получаване на крайния резултат

Намаляването на аргумента обикновено се основава на добре известни тригонометрични идентичности, които могат да се търсят в различни стандартни препратки като MathWorld (http://mathworld.wolfram.com/InverseTangent.html). За изчисляването на arctan, често използваните идентичности са

  • arctan (-x) = -arctan (x)
  • arctan (1/x) = 0,5 * pi - arctan(x) [x > 0]
  • arctan (x) = arctan(c) + arctan((x - c) / (1 + x*c))

Имайте предвид, че последната идентичност се поддава на изграждането на таблица със стойности arctan(i/2n), i = 1...2n, което позволява използването на произволно тесен първичен интервал на приближение за сметка на допълнително съхранение на таблица. Това е класически програмен компромис между пространство и време.

Апроксимацията на основния интервал обикновено е минимаксна полиномна апроксимация с достатъчна степен. Рационалните приближения обикновено не са конкурентни на модерен хардуер поради високата цена на разделянето с плаваща запетая и също така страдат от допълнителна числена грешка поради изчисляването на два полинома плюс грешката, причинена от деленето.

Коефициентите за минимаксни полиномни приближения обикновено се изчисляват с помощта на алгоритъма на Remez (http://en.wikipedia.org/wiki/Remez_algorithm). Инструменти като Maple и Mathematica имат вградени средства за изчисляване на такива приближения. Точността на полиномните приближения може да се подобри, като се уверите, че всички коефициенти са точно представими машинни числа. Единственият инструмент, за който знам, че има вградено средство за това, е Sollya (http://sollya.gforge.inria.fr/), който предлага функция fpminimax().

Оценяването на полиноми обикновено използва схемата на Horner (http://en.wikipedia.org/wiki/Horner%27s_method), който е ефективен и точен, или смес от схемата на Estrin (http://en.wikipedia.org/wiki/Estrin%27s_scheme) и на Horner. Схемата на Естрин позволява отлично използване на паралелизма на ниво инструкции, осигурен от суперскаларни процесори, с незначително въздействие върху общия брой инструкции и често (но не винаги) благоприятно въздействие върху точността.

Използването на FMA (слято умножаващо добавяне) подобрява точността и производителността на всяка схема за оценка поради намаления брой стъпки на закръгляване и като предлага известна защита срещу субтрактивно анулиране. FMA се намира на много процесори, включително GPU и скорошни x86 CPU. В стандартен C и стандартен C++ операцията FMA е изложена като fma() стандартна библиотечна функция, но трябва да се емулира на платформи, които не предлагат хардуерна поддръжка, което я прави бавна на тези платформи.

От гледна точка на програмирането бихме искали да избегнем риска от грешки при преобразуване, когато превеждаме константите с плаваща запетая, необходими за приближаването и редуцирането на аргументи от текстово към машинно представяне. Процедурата за преобразуване на ASCII към плаваща запетая е известна с това, че съдържа трудни грешки (напр. http://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/). Един механизъм, предлаган от стандартния C (не C++, който най-добре познавам, където е достъпен само като собствено разширение) е да се уточнят константите с плаваща запетая като шестнадесетични литерали, които директно изразяват основния битов модел, ефективно избягване на сложни преобразувания.

По-долу е C код за изчисляване на arctan() с двойна точност, който демонстрира много от принципите и техниките на проектиране, споменати по-горе. На този бързо конструиран код липсва сложността на реализациите, посочени в други отговори, но трябва да осигури резултати с по-малко от 2 ulp грешки, което може да е достатъчно в различни контексти. Създадох персонализирано минимаксно приближение с проста реализация на алгоритъма на Remez, който използва 1024-битова аритметика с плаваща запетая за всички междинни стъпки. Бих очаквал използването на Sollya или подобни инструменти да доведе до числено по-добри приближения.

double my_atan (double x)
{
    double a, z, p, r, s, q, o;
    /* argument reduction: 
       arctan (-x) = -arctan(x); 
       arctan (1/x) = 1/2 * pi - arctan (x), when x > 0
    */
    z = fabs (x);
    a = (z > 1.0) ? 1.0 / z : z;
    /* evaluate minimax polynomial approximation */
    s = a * a; // a**2
    q = s * s; // a**4
    o = q * q; // a**8
    /* use Estrin's scheme for low-order terms */
    p = fma (fma (fma (-0x1.53e1d2a25ff34p-16, s, 0x1.d3b63dbb65af4p-13), q,
                  fma (-0x1.312788dde0801p-10, s, 0x1.f9690c82492dbp-9)), o,
             fma (fma (-0x1.2cf5aabc7cef3p-7, s, 0x1.162b0b2a3bfcep-6), q, 
                  fma (-0x1.a7256feb6fc5cp-6, s, 0x1.171560ce4a483p-5)));
    /* use Horner's scheme for high-order terms */
    p = fma (fma (fma (fma (fma (fma (fma (fma (fma (fma (fma (fma (p, s,
        -0x1.4f44d841450e1p-5), s,
         0x1.7ee3d3f36bb94p-5), s, 
        -0x1.ad32ae04a9fd1p-5), s,  
         0x1.e17813d66954fp-5), s, 
        -0x1.11089ca9a5bcdp-4), s,  
         0x1.3b12b2db51738p-4), s,
        -0x1.745d022f8dc5cp-4), s,
         0x1.c71c709dfe927p-4), s,
        -0x1.2492491fa1744p-3), s,
         0x1.99999999840d2p-3), s,
        -0x1.555555555544cp-2) * s, a, a);
    /* back substitution based on argument reduction */
    r = (z > 1.0) ? (0x1.921fb54442d18p+0 - p) : p;
    return copysign (r, x);
}
person njuffa    schedule 16.04.2014
comment
От любопитство, има ли случаи, при които използването на радиани за тригонометрични изчисления позволява много по-добра точност, отколкото би било постижимо при използване на цял брой подразделения? Със сигурност редуцирането на модула би било по-лесно и по-точно, като се използват ъгли, измерени в градуси, квадранти или цели кръгове. Знам защо радианите са полезни в смятането, но това, че броят на ъгловите единици за пълен кръг не може да се представи точно, изглежда доста неприятно. - person supercat; 16.04.2014
comment
Някои платформи предлагат sinpi() и cospi() функции, които приемат аргументи, кратни на pi, което прави намаляването на аргументите лесно. В противен случай точното намаляване на аргумента за sin, cos, tan е трудно и по същество изисква междинно изчисление с много точност, независимо дали се използват радиани или градуси. Каноничната препратка е: Mary H. Payne and Robert N. Hanek, Radian Reduction for Trigonometric Functions, ACM SIGNUM Newsletter, vol. 18, бр. 1, януари 1983 г., стр. 19 - 24 - person njuffa; 17.04.2014
comment
Придружаващият документ за намаляване на аргумента на степента е: Мери Х. Пейн и Робърт Н. Ханек, Намаляване на степента за тригонометрични функции, Бюлетин на ACM SIGNUM, том. 18. бр. 2, април 1983 г., стр. 18 - 19 - person njuffa; 17.04.2014
comment
Защо би се изисквала редукция с много точност в случая на градуси? Разбира се, по-лесно е в случай на множество от pi, но fpmod(x, 360.0) е указано да бъде абсолютно прецизно за всички стойности на x, нали? Между другото, не съм сигурен колко полезна е свръхпрецизната редукция на аргумента при използване на радиани; ако някой се опитва да изчисли sin(2πx) с помощта на Math.Sin(x*2.0*Math.Pi), резултатът ще бъде по-точен, ако намаляването на аргумента се извърши по модул 2.0*Math.Pi, отколкото ако се извърши по модул 2π. - person supercat; 17.04.2014
comment
Може да съм се объркал за намаляването на степента (днес малко бързам). Никога не съм имал нужда да го прилагам и следователно не съм мислил за това. Това, което казвате, изглежда има смисъл: ако е налична операция за остатък IEEE с естествена точност, това трябва да е всичко, което е необходимо за точно намаляване. IMHO, най-доброто решение за изчисляване на термини като sin(2πx) е да се предложи функция sinpi(), така че програмистите да могат да напишат sinpi(2*x) и да получат резултат, който е възможно най-съвместим с математическото поведение. Използването на машинен PI въвежда фазова грешка. - person njuffa; 17.04.2014
comment
Техниките, с които съм запознат за изчисляване на тригонометрични функции, включват започване с преобразуване на ъгъл в степен на две, част от окръжност; знаете ли някакви техники, които не знаят? Ако не, имате ли представа защо функциите sinpi и т.н. не трябва да са универсално достъпни? Програмистът да мащабира стойност с коефициент 2pi, така че процесорът да може да я намали с коефициент pi, изглежда лудост. - person supercat; 17.04.2014
comment
Много се отклоняваме от темата (Stackoverflow не е предназначен за дискусии). Стандартите обикновено кодифицират съществуващото използване. IEEE-754 споменава sinpi и т.н. като препоръчителна функционалност, някои C/C++ вериги от инструменти го предлагат като разширение, а средите за програмиране на GPU като CUDA и OpenCL го включват. Така че, ако програмистите продължат да я използват и изискват, бих очаквал това да бъде стандартна библиотечна функция след няколко десетилетия. - person njuffa; 17.04.2014
comment
Не съм съгласен с точното намаляване на аргумента за sin, cos, tan е трудно ... независимо ... или се използват градуси. reduced_degrees = fmod(raw_degrees, 360.0) е директно лесно намаляване на обхвата. Справка stackoverflow.com /questions/20928253/ - person chux - Reinstate Monica; 03.03.2017
comment
@chux Съгласен съм, че намаляването на аргумента на тригонометричната функция по степен е лесно. За съжаление няма начин да се коригира коментар (освен по време на гратисния период), когато някой се е изразил погрешно. Все пак бих предложил remquo (angle,90.0) вместо fmod(). - person njuffa; 03.03.2017
comment
Съгласете се, че remquo() е още по-добър - въпреки че мисля, че това е добавка C99, използвах remquo() успешно тук за подобрена sind() - person chux - Reinstate Monica; 03.03.2017

Резюме: Трудно е. Освен това Ерик Постписчил и Стивън Кенън, които понякога се мотаят около SO, са много добри в това.

Обичайният подход за много специални функции е както следва:

  • Обработвайте NaN, безкрайности и нули със знак като специални случаи.
  • Ако числото е толкова голямо, че резултатът се закръгля до M_PI, върнете M_PI. Наречете този праг M.
  • Ако има някаква идентичност за намаляване на аргумента, използвайте я, за да приведете аргумента в по-добър диапазон. (Това може да е сложно: За sin и cos това означава, че избирате кратно на точната стойност на 2pi, така че да попаднете в правилния диапазон.)
  • Разбийте [0,M) на ограничен брой интервали. Използвайте приближение на Чебишев за арктан от доста висок порядък за всеки интервал. (Това се прави офлайн и обикновено е източникът на всички магически числа, които виждате в тези реализации. Също така, човек може леко да затегне приближението на Чебишев, като използва алгоритъма за обмен на Remez, но не знам за случаи, в които това помага много .)
  • Разберете в кой интервал е аргументът (използвайки ifs и други неща или просто трик с индексиране на таблица) и оценете серията на Чебишев на този интервал.

Няколко свойства са особено желани тук:

  • Реализацията arctan трябва да бъде монотонна; тоест, ако x < y, тогава arctan(x) <= arctan(y).
  • Реализацията arctan трябва винаги да връща отговор в рамките на 1 ulp от правилния отговор. Обърнете внимание, че това е свързана с относителна грешка.

Не е напълно лесно да се оцени ред на Чебишев, така че тези две свойства да са валидни. Тук често се срещат трикове, при които две doubles се използват за представяне на различни части от една стойност. Тогава вероятно има някои казуси, които да покажат, че внедряването е монотонно. Също така, близо до нула, приближение на Тейлър до arctan вместо приближение на Чебишев --- търсите относителна грешка и оценяването на серията с помощта на правилото на Хорнър трябва да работи.

Ако търсите atan имплементация за четене, fdlibm изглежда по-малко гаден от този, който в момента е в glibc. Намаляването на аргумента изглежда се основава на самоличността на тригонометра tan(a+b) = (tan(a) + tan(b)) / (1 - tan(a) tan(b)), използвайки 0.5, 1 или 1.5 за tan(a) според случая.

person tmyklebu    schedule 13.04.2014
comment
Тъй като сме на темата и може би трябва да задам това в друг въпрос, добра причина да използваме апроксимант на Паде вместо полиномиален е, когато функцията за апроксимиране, като например аркутангенс, клони към крайна граница в +/- инф. Очевидно полиномно приближение със степен по-голяма от 1 никога няма да е добро там. Сега въпросът, който имам, е, тъй като така или иначе правим намаляване на аргумента и приближението се използва само за, да речем [0 … 0,5], тогава горната причина (единствената, която някога съм чувал) не би трябвало да има толкова голямо значение, трябва ли? - person Pascal Cuoq; 14.04.2014
comment
@PascalCuoq: Бих очаквал приближението на Чебишев на степен k и приближението на Паде-Чебишев на общата степен (степен на числител + степен на знаменател) k да бъдат приблизително еднакво добри при приближаване на функция с добро поведение в компактен интервал. При липсата на такава схема за редуциране на аргументи, предполагам, че трябва да направите правилната разлика в степените. (Някога ми се е налагало да пиша нискокачествени реализации на специални функции, така че може да има по-фини причини да използвам рационално приближение вместо полиномно приближение в някои случаи --- не знам.) - person tmyklebu; 15.04.2014
comment
Рационалните приближения рядко са конкурентни. Делението с плаваща запетая е много по-скъпо от FADD, FMUL или FMA. Също така, трябва да се справите с грешка от два полинома плюс грешката от деленето. В повечето случаи бихте искали или прави полиноми, или таблица плюс полином. По отношение на полиномите бихте искали коефициенти, оптимизирани за целевата прецизност, напр. приближения, предоставени от функцията fpminimax() на Sollya. Ако е наличен FMA, това ще помогне да се запази малка грешката при оценката. Схемата на Estrin може да помогне с производителността на суперскаларни архитектури. - person njuffa; 15.04.2014