Как осуществляется арктан?

Многие реализации библиотеки идут глубоко в FPATAN instuction для всех дуговых функций. Как внедрен FPATAN? Предполагая, что у нас есть 1 битовый знак, M бит mantissa и N бит exponent, каков алгоритм, чтобы получить арктангенс этого числа? Там должен быть такой алгоритм, поскольку FPU делает это.

Ответ 1

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

Вот реализация 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 делает все это в некоторых цепях, поэтому CPU не должен выполнять всю эту работу.

Ответ 2

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

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

Сокращение аргументов обычно основано на хорошо известных тригонометрических тождествах, которые можно искать в различных стандартных ссылках, таких как 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/2 n), я = 1... 2 n что позволяет использовать произвольно узкий первичный интервал аппроксимации за счет дополнительного хранения таблиц. Это классический программный компромисс между пространством и временем.

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

Коэффициенты для минимаксных полиномиальных аппроксимаций обычно вычисляются с использованием алгоритма Ремеза (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) и Horner's. Схема Estrin позволяет отлично использовать уровень команд parallelism, обеспечиваемый суперскалярными процессорами, незначительно влияя на общий подсчет команд и часто (но не всегда) на доброкачественное воздействие на точность.

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

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

Ниже приведен код C для вычисления double-precision 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 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 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);
}

Ответ 3

Резюме: Это сложно. Кроме того, Eric Postpischil и Stephen Canon, которые иногда висят вокруг SO, очень хороши в этом.

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

  • Обрабатывать NaNs, бесконечности и подписанные нули в качестве особых случаев.
  • Если число настолько велико, что результат округляется до M_PI, верните M_PI. Вызовите этот порог M.
  • Если есть какой-либо идентификатор сокращения аргументов, используйте его, чтобы привести аргумент в более удобный диапазон. ( Это может быть сложным: для sin и cos это означает, что вы выбрали кратное точное значение 2pi, чтобы вы приземлились в правильном диапазоне.)
  • Разбить [0,M) на конечное число интервалов. Используйте Чебышевское приближение до arctan достаточно высокого порядка на каждом интервале. (Это делается в автономном режиме, и обычно это источник всех магических чисел, которые вы видите в этих реализациях. Кроме того, можно слегка приблизить приближение Чебышева, используя алгоритм обмена Remez, но мне неизвестны случаи, когда это помогает.)
  • Определите интервал, в котором находится аргумент (с использованием if и т.д. или просто трюк с индексированием таблицы), и оцените серию Чебышева на этом интервале.

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

  • Реализация 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), если это необходимо.