Как реализован арктан?

Многие реализации библиотеки уходят вглубь к инструкции 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 обычно реализуется с помощью микрокода, то есть небольшой программы, хранящейся во внутреннем ПЗУ внутри процессора. Хотя такие программы могут использовать специальные операции, недоступные в видимой ISA, обычно для этого не используются специальные схемы. - person njuffa; 16.04.2014
comment
вторая реализация atan2 намного короче, поскольку использует atan. - person lrineau; 16.04.2014

Реализации инструкций FPATAN в процессорах x86 обычно являются проприетарными. Чтобы вычислить арктангенс или другие (обратные) тригонометрические функции, общие алгоритмы следуют трехэтапному процессу:

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

Сокращение аргументов обычно основано на хорошо известных тригонометрических тождествах, которые можно найти в различных стандартных справочниках, таких как MathWorld (http://mathworld.wolfram.com/InverseTangent.html). Для вычисления арктангенса обычно используются следующие тождества:

  • арктангенс (-х) = -арктангенс (х)
  • 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, которая позволяет использовать сколь угодно узкого интервала первичной аппроксимации за счет дополнительного табличного хранения. Это классический компромисс программирования между пространством и временем.

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

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

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

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

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

Ниже приведен код C для вычисления arctan() с двойной точностью, который демонстрирует многие из принципов и методов проектирования, упомянутых выше. Этому быстро сконструированному коду не хватает сложности реализаций, указанных в других ответах, но он должен давать результаты с ошибкой менее 2 ulps, чего может быть достаточно в различных контекстах. Я создал собственное минимаксное приближение с простой реализацией алгоритма Ремеза, который использовал 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(), которые принимают аргументы, кратные числу пи, что упрощает сокращение аргументов. В противном случае точное сокращение аргумента для sin, cos, tan затруднено и по существу требует промежуточных вычислений с множественной точностью независимо от того, используются ли радианы или градусы. Каноническая ссылка: Мэри Х. Пейн и Роберт Н. Ханек, Редукция радиана для тригонометрических функций, Информационный бюллетень ACM SIGNUM, том. 18, нет. 1, январь 1983 г., стр. 19–24. - person njuffa; 17.04.2014
comment
Сопутствующая статья по уменьшению аргумента степени: Мэри Х. Пейн и Роберт Н. Ханек, Сокращение степени для тригонометрических функций, Информационный бюллетень ACM SIGNUM, том. 18. нет. 2, апрель 1983 г., стр. 18–19. - person njuffa; 17.04.2014
comment
Почему в случае градусов требуется редукция с множественной точностью? Безусловно, это проще в случае кратных пи, но 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 с собственной точностью, это должно быть все, что необходимо для точного сокращения. ИМХО, лучшее решение для вычисления таких терминов, как sin(2πx), — это предложить функцию sinpi(), чтобы программисты могли написать sinpi(2*x) и получить результат, максимально совместимый с математическим поведением. Использование машинного PI приводит к фазовой ошибке. - person njuffa; 17.04.2014
comment
Методы, с которыми я знаком для вычисления триггерных функций, включают в себя преобразование угла в долю окружности, равную степени двойки; Вы знаете какие-нибудь методы, которые этого не делают? Если нет, есть ли у вас какие-либо идеи, почему sinpi и т. д. функции не должны быть общедоступными? Увеличение программистом значения в 2 раза, чтобы процессор мог уменьшить его в 2 раза, кажется безумием. - person supercat; 17.04.2014
comment
Мы уходим от темы (Stackoverflow не предназначен для дискуссий). Стандарты обычно кодифицируют существующее использование. IEEE-754 упоминает sinpi и т. д. в качестве рекомендуемой функциональности, некоторые наборы инструментов C/C++ предлагают ее в качестве расширения, а среды программирования графических процессоров, такие как CUDA и OpenCL, включают ее. Поэтому, если программисты продолжат использовать и запрашивать ее, я ожидаю, что через пару десятилетий она станет стандартной библиотечной функцией. - person njuffa; 17.04.2014
comment
Не согласен с точным сокращением аргумента для sin, cos, tan трудно ... независимо от ... или используются степени. reduced_degrees = fmod(raw_degrees, 360.0) — это прямое легкое сокращение дальности. Ref stackoverflow.com /вопросы/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) на конечное число интервалов. Используйте аппроксимацию Чебышева для арктангенса довольно высокого порядка на каждом интервале. (Это делается в автономном режиме, и обычно это источник всех магических чисел, которые вы видите в этих реализациях. Кроме того, можно немного подтянуть аппроксимацию Чебышева, используя алгоритм обмена Ремеза, но я не знаю ни одного случая, когда это сильно помогает. .)
  • Выясните, в каком интервале находится аргумент (используя ifs и прочее, или просто трюк с индексацией таблицы), и оцените ряд Чебышева на этом интервале.

Здесь особенно желательны несколько свойств:

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

Не совсем просто вычислить ряд Чебышева так, чтобы выполнялись эти два свойства. Здесь распространены приемы, когда два double используются для представления разных частей одного значения. Тогда, вероятно, есть некоторые примеры, чтобы показать, что реализация монотонна. Кроме того, около нуля приближение Тейлора к 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(). Если доступен FMA, это поможет уменьшить ошибку оценки. Схема Эстрина может помочь повысить производительность на суперскалярных архитектурах. - person njuffa; 15.04.2014