Как C вычисляет sin () и другие математические функции?

Я просматривал разборки .NET и исходный код GCC, но, похоже, нигде не могу найти фактическую реализацию sin() и другие математические функции... они всегда ссылаются на что-то другое.

может кто-нибудь помочь мне найти их? Я чувствую, что маловероятно, что все оборудование, на котором будет работать C, поддерживает функции trig в оборудовании, поэтому должен быть программный алгоритм где-то, да?


Я знаю несколько способы функционирования can быть вычислены, и написал свои собственные процедуры для вычисления функций с использованием серии Тейлора для удовольствия. Мне интересно, как это делают реальные языки производства, поскольку все мои реализации всегда на несколько порядков медленнее, хотя я думаю, что мои алгоритмы довольно умны (очевидно, что это не так).

20 ответов


в GNU libm, реализация sin зависит от системы. Поэтому вы можете найти реализацию для каждой платформы где-то в соответствующем подкаталоге sysdeps.

один каталог включает реализацию на языке C, предоставленную IBM. С октября 2011 года, это код, выполняющийся при вызове sin() в типичной системе x86-64 Linux. Это, по-видимому, быстрее, чем fsin инструкция по монтажу. Исходный код: sysdeps/ieee754/dbl-64 / s_sin.c искать __sin (double x).

этот код очень сложен. Ни один программный алгоритм не является максимально быстрым, а также точным во всем диапазоне x значения, поэтому библиотека реализует множество различных алгоритмов, и ее первая задача-посмотреть на x и решить, какой алгоритм использовать. В некоторых регионах он использует то, что выглядит как привычный ряд Тейлора. Несколько алгоритмов сначала вычисляют быстрый результат, затем если это недостаточно точно, отбросьте его и вернитесь к более медленному алгоритму.

более старые 32-разрядные версии GCC / glibc использовали fsin инструкция, которая удивительно неточна для некоторых входов. Есть увлекательный блоге, иллюстрируя это всего 2 строки кода.

реализация fdlibm sin в чистом C намного проще, чем glibc, и хорошо прокомментирован. Исходный код: fdlibm / s_sin.c и fdlibm / k_sin.c


OK детки, время для профессионалов.... Это одна из моих самых больших жалоб на неопытных инженеров-программистов. Они приходят к расчету трансцендентальных функций с нуля (используя ряд Тейлора), как будто никто никогда не делал этих вычислений раньше в своей жизни. Неправда. Это хорошо определенная проблема, к которой тысячи раз подходили очень умные инженеры по программному и аппаратному обеспечению, и у нее есть четко определенное решение. В основном, большинство трансцендентальных функций используют Полиномы Чебышева для их вычисления. Что касается того, какие многочлены используются, зависит от обстоятельств. Во-первых, Библия по этому вопросу-это книга под названием "компьютерные приближения" Харта и Чейни. В этой книге вы можете решить, есть ли у вас аппаратный сумматор, множитель, делитель и т. д., и решить, какие операции являются самыми быстрыми. например, если у вас действительно быстрый делитель, самый быстрый способ вычисления синуса может быть P1(x)/P2(x), где P1, P2-многочлены Чебышева. Без быстрого разделителя, это могло бы быть просто P (x), где P имеет гораздо больше членов, чем P1 или P2....так будет медленнее. Итак, первый шаг-определить ваше оборудование и то, что оно может сделать. Затем вы выбираете соответствующую комбинацию полиномов Чебышева(обычно имеет вид cos(ax) = aP (x) для Косинуса, например, снова, где P-полином Чебышева). Затем вы решаете, какую десятичную точность вы хотите. например, если вы хотите точность 7 цифр, вы посмотрите это в соответствующей таблице в книге, которую я упомянул, и она даст вам (для точность = 7.33) число N = 4 и полиномиальное число 3502. N-порядок многочлена (так что это p4.x^4 + p3.x^3 + p2.x^2 + p1.x + p0), потому что N=4. Затем вы просматриваете фактическое значение значений p4,p3,p2,p1,p0 в задней части книги под 3502 (они будут в плавающей точке). Затем вы реализуете свой алгоритм в программном обеспечении в виде: (((П4.x + p3).x + p2).x + p1).х + Р0 ....и вот как вы вычисляете Косинус до 7 десятичных знаков на этом оборудовании.

обратите внимание, что большинство аппаратные реализации трансцендентальных операций в FPU обычно включают в себя некоторый микрокод и подобные операции (зависит от аппаратного обеспечения). Полиномы Чебышева используются для большинства трансцендентальных, но не для всех. например, квадратный корень быстрее использовать двойную итерацию метода Ньютона рафсона, используя сначала таблицу поиска. Опять же, эта книга "компьютерные приближения" скажет вам об этом.

Если вы планируете внедрить эти функции, я бы рекомендовал всем, чтобы они получили копию эта книга. Это действительно библия для такого рода алгоритмов. Обратите внимание, что есть пучки альтернативных средств для вычисления этих значений, таких как cordics и т. д., Но они, как правило, лучше всего подходят для конкретных алгоритмов, где вам нужна только низкая точность. Чтобы гарантировать точность каждый раз, многочлены Чебышева-это путь. Как я уже сказал, Проблема четко определена. Решается уже 50 лет.....вот как это делается.

теперь, как говорится, есть методы, посредством которых полиномы Чебышева могут быть использованы для получения единственного результата точности с полиномом низкой степени (как пример для Косинуса выше). Затем существуют другие методы интерполяции между значениями для повышения точности без перехода к гораздо большему многочлену, например "метод точных таблиц Gal". Этот последний метод является то, что сообщение, относящееся к литературе ACM, имеет в виду. Но в конечном счете, полиномы Чебышева-это то, что используется для получения 90% пути там.

наслаждайтесь.


функции как синус и косинус снабжены в микрокоде внутри микропроцессоров. Например, чипы Intel имеют инструкции по сборке для них. Компилятор будет генерировать код, вызывающий эти инструкции по сборке. (Напротив, компилятор Java не будет. Java оценивает тригонометрические функции в программном обеспечении, а не в аппаратном, и поэтому работает намного медленнее.)

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


Да, существуют программные алгоритмы для вычисления sin тоже. В принципе, вычисление такого рода вещей с помощью цифрового компьютера обычно выполняется с помощью численные методы как аппроксимирующая ряд Тейлора представление функции.

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


Это сложный вопрос. Intel-подобный процессор семейства x86 имеет аппаратную реализацию sin() функция, но она является частью FPU x87 и больше не используется в 64-битном режиме (где вместо этого используются регистры SSE2). В этом режиме используется программная реализация.

существует несколько таких реализаций. В fdlibm и используется в Java. Насколько мне известно, реализация glibc содержит части fdlibm и другие части предоставлено IBM.

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


использовать ряд Тейлора и попробуйте найти связь между терминами серии, чтобы вы не вычисляли вещи снова и снова

вот пример для cosinus:

double cosinus(double x,double prec)
{
    double t , s ;
    int p;
    p = 0;
    s = 1.0;
    t = 1.0;
    while(fabs(t/s) > prec)
    {
        p++;
        t = (-t * x * x) / ((2 * p - 1) * (2 * p));
        s += t;
    }
    return s;}

используя это, мы можем получить новый член суммы, используя уже используемый (мы избегаем факториала и x^2p)

объяснение http://img514.imageshack.us/img514/1959/82098830.jpg


многочлены Чебышева, как упоминалось в другом ответе, являются многочленами, где наибольшая разница между функцией и многочленом как можно меньше. Это отличное начало.

в некоторых случаях, максимальная ошибка не то, что вас интересует, а максимальная относительная ошибка. Например, для функции синуса ошибка около x = 0 должна быть намного меньше, чем для больших значений; вы хотите маленький относительные ошибка. Так что ... вычислите полином Чебышева для sin x / x и умножьте этот полином на x.

Далее вам нужно выяснить,как оценить многочлен. Вы хотите оценить его таким образом, что промежуточные значения малы и поэтому ошибки округления маленькие. В противном случае ошибки округления могут стать намного больше, чем ошибки в полиноме. И с такими функциями, как функция синуса, если вы небрежны, то возможно, что результат, который вы вычисляете для греха x больше, чем результат для sin y, даже если x

например, sin x = x-x^3/6 + x^5 / 120-x^7 / 5040... Если вы наивно вычисляете sin x = x * (1 - x^2/6 + x^4/120 - x^6/5040...), то эта функция в скобках уменьшается, и она будет случается, что если y-следующее большее число к x, то иногда sin y будет меньше, чем sin x. Вместо этого вычислите sin x = x-x^3 * (1/6 - x^2 / 120 + x^4/5040...), где это не может произойти.

при вычислении многочленов Чебышева обычно необходимо округлить коэффициенты до двойной точности, например. Но в то время как полином Чебышева оптимален, полином Чебышева с коэффициентами, округленными до двойной точности, не является оптимальным полиномом с коэффициентами двойной точности!

например, для sin (x), где вам нужны коэффициенты для x, x^3, x^5, x^7 и т. д. Вы делаете следующее: вычислите наилучшее приближение sin x с полиномом (ax + bx^3 + cx^5 + dx^7) с более высокой точностью, затем округлите a до двойной точности, давая A. разница между a и A будет довольно большой. Теперь вычислите наилучшее приближение (sin x-Ax) с полиномом (b x^3 + cx^5 + dx^7). Вы получаете разные коэффициенты, потому что они адаптируются к разнице между A и A. раунд b для двойной точности B. Затем приближается (sin x-Ax - Bx^3) с полиномом cx^5 + dx^7 и так далее. Вы получите полином, который почти так же хорош, как исходный полином Чебышева, но намного лучше, чем Чебышев, округленный до двойной точности.

Далее следует учесть ошибки округления при выборе полинома. Вы нашли полином с минимальной ошибкой в полиноме, игнорируя ошибку округления, но вы хотите оптимизировать полином плюс ошибка округления. После того, как у вас есть полином Чебышева, вы можете вычислить границы ошибки округления. Скажем, f (x) - ваша функция, P (x) - многочлен, А E (x) - ошибка округления. Вы не хотите оптимизировать | f (x) - P (x) |, вы хотите оптимизировать | f (x) - P (x) +/- E (x) |. Вы получите немного другой полином, который пытается сохранить полиномиальные ошибки там, где ошибка округления велика, и немного расслабляет полиномиальные ошибки там, где ошибка округления мала.

все это позволит вам легко округлять ошибки не более 0,55 раза последний бит, где +, -,*, / имеют ошибки округления не более 0,50 последнего бита.


на sin в частности, использование расширения Тейлора даст вам:

sin (x): = x - x^3/3! + x^5/5! - x^7/7! + ... (1)

вы будете продолжать добавлять термины, пока разница между ними не будет ниже принятого уровня допуска или просто для конечного количества шагов (быстрее, но менее точно). Примером может быть что-то вроде:

float sin(float x)
{
  float res=0, pow=x, fact=1;
  for(int i=0; i<5; ++i)
  {
    res+=pow/fact;
    pow*=-1*x*x;
    fact*=(2*(i+1))*(2*(i+1)+1);
  }

  return res;
}

Примечание: (1) работает из-за апроксимации sin (x)=x для малых углов. Для больших углов вам нужно рассчитать все больше и больше сроков, чтобы получить приемлемые результаты. Вы можете использовать аргумент while и продолжить для определенной точности:

double sin (double x){
    int i = 1;
    double cur = x;
    double acc = 1;
    double fact= 1;
    double pow = x;
    while (fabs(acc) > .00000001 &&   i < 100){
        fact *= ((2*i)*(2*i+1));
        pow *= -1 * x*x; 
        acc =  pow / fact;
        cur += acc;
        i++;
    }
    return cur;

}

фактическая реализация библиотечных функций зависит от конкретного компилятора и / или поставщика библиотеки. Будь то аппаратное или программное обеспечение, будь то расширение Тейлора или нет и т. д., будет меняться.

Я понимаю, что это абсолютно не помогает.


относительно тригонометрической функции как sin(), cos(),tan() после 5 лет не было упоминания о важном аспекте высококачественных функций тригонометрии:уменьшение диапазона.

ранним шагом в любой из этих функций является уменьшение угла в радианах до диапазона интервала 2 * π. Но π иррационально настолько простые сокращения, как x = remainder(x, 2*M_PI) ввести ошибку как M_PI, или машина pi, является приближением π. Итак, как это сделать x = remainder(x, 2*π)?

ранние библиотеки использовали расширенную точность или созданное Программирование, чтобы дать качественные результаты, но все еще в ограниченном диапазоне double. Когда запрашивается большое значение, например sin(pow(2,30)), результаты были бессмысленными или 0.0 и, возможно, с флаг ошибки установить что-то вроде TLOSS полная потеря точности или PLOSS частичная потеря точности.

хорошее уменьшение диапазона больших значений до интервала, такого как-π до π, является сложной задачей проблема, которая соперничает с проблемами основной функции триггера, как sin() сам.

хороший отчет уменьшение аргументов для огромных аргументов: хорошо до последнего бита (1992). Он хорошо освещает проблему: обсуждает необходимость и как все было на разных платформах (SPARC, PC, HP, 30+ other) и предоставляет алгоритм решения, который дает качественные результаты для все double С -DBL_MAX to DBL_MAX.


если оригинал аргументы в градусах, но могут иметь большое значение, используйте fmod() сперва для улучшенной точности. Хороший fmod() представит ошибка и так обеспечивают превосходное уменьшение ряда.

// sin(degrees2radians(x))
sin(degrees2radians(fmod(x, 360.0))); // -360.0 <= fmod(x,360) <= +360.0

различные тригонометрические тождества и remquo() предложите даже больше улучшения. Пример: sind ()


Они обычно реализуются в программном обеспечении и не будут использовать соответствующие аппаратные (то есть aseembly) вызовы в большинстве случаев. Однако, как отметил Джейсон, они специфичны для реализации.

обратите внимание, что эти программные процедуры не являются частью источников компилятора, а скорее будут найдены в библиотеке correspoding, такой как clib или glibc для компилятора GNU. Видеть http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions

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


Если вам нужна реализация в программном обеспечении, а не в аппаратном обеспечении, место для поиска окончательного ответа на этот вопрос-Глава 5 Численные Рецепты. Моя копия находится в коробке, поэтому я не могу дать подробности, но короткая версия (если я правильно помню) заключается в том, что вы берете tan(theta/2) Как ваша примитивная операция и вычислить другие оттуда. Вычисление выполняется с приближением ряда, но это то, что сходится много быстрее, чем Тейлор серии.

Извините, я не могу вспомнить больше, не получив мою руку на книгу.


как указывали многие люди, это зависит от реализации. Но насколько я понимаю ваш вопрос, вас интересует реальная программа выполнению математических функций, но просто не удалось найти. Если это так, то вот, пожалуйста:

  • загрузить исходный код glibc из http://ftp.gnu.org/gnu/glibc/
  • посмотреть в файле dosincos.c расположенном в распакованный корень glibc\sysdeps\ieee754\dbl-64 папка
  • аналогично вы можете найти реализации остальной части математической библиотеки, просто найдите файл с соответствующим именем

вы также можете посмотреть файлы с помощью .tbl расширение, их содержимое не более чем огромные таблицы предварительно значения различных функций в двоичном виде. Вот почему реализация так быстра: вместо вычисления всех коэффициентов любого ряда, который они используют, они просто делают быстрый поиск, который много быстрее. Кстати, они используют индивидуальные серии для вычисления синуса и Косинуса.

надеюсь, это поможет.


Я постараюсь ответить на случай sin() в программе C, скомпилированной с компилятором GCC C на текущем процессоре x86 (скажем, Intel Core 2 Duo).

в языке C стандартная библиотека C включает общие математические функции, не включенные в сам язык (например,pow, sin и cos для силы, синуса, и Косинуса соответственно). Заголовки которых включены в математика.h.

теперь в системе GNU / Linux эти библиотеки функции предоставляются glibc (GNU libc или GNU C Library). Но компилятор GCC хочет, чтобы вы ссылались на математическая библиотека (libm.so) С помощью -lm флаг компилятора для включения использования этих математических функций. я не уверен, почему он не является частью стандартной библиотеки C. это будет версия программного обеспечения функций с плавающей запятой или"soft-float".

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

теперь компилятор может оптимизировать стандартную функцию библиотеки C sin() (предоставлен libm.so), который будет заменен вызовом собственной инструкции к встроенной функции Sin () вашего CPU/FPU, которая существует как инструкция FPU (FSIN для x86 / x87) на более новых процессорах, таких как Core 2 series (это правильно почти так же, как i486DX). Это будет зависеть от флагов оптимизации, переданных компилятору gcc. Если бы компилятору было сказано написать код, который будет выполняться на любом процессоре i386 или новее, он не сделал бы такой оптимизации. The -mcpu=486 флаг сообщит компилятору, что такая оптимизация безопасна.

теперь, если программа выполнила версию программного обеспечения функции sin (), она будет делать это на основе CORDIC (Координата Вращения Цифровой компьютер) или алгоритм BKM или больше вероятно, расчет таблицы или степенных рядов, который обычно используется сейчас для расчета таких трансцендентных функций. [Src:http://en.wikipedia.org/wiki/Cordic#Application]

любые последние (начиная с 2.9 X прибл.) версия gcc также предлагает встроенную версию sin,__builtin_sin() что он будет использоваться для замены стандартного вызова версии библиотеки C в качестве оптимизации.

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


нет ничего лучше, чем поразить источник и увидеть, как кто-то на самом деле сделал это в общей библиотеке; давайте рассмотрим одну реализацию библиотеки C в частности. Я выбрал uLibC.

вот функция sin:

http://git.uclibc.org/uClibc/tree/libm/s_sin.c

который выглядит так, как будто он обрабатывает несколько особых случаев, а затем выполняет некоторое уменьшение аргумента для отображения ввода в диапазон [- pi/4, pi / 4], (Разделение аргумента на две части, большую часть и хвост) перед вызовом

http://git.uclibc.org/uClibc/tree/libm/k_sin.c

который затем работает на этих двух частях. Если хвоста нет, приближенный ответ генерируется с использованием полинома степени 13. Если есть хвост, вы получаете небольшое корректирующее дополнение, основанное на принципе, что sin(x+y) = sin(x) + sin'(x')y


всякий раз, когда такая функция оценивается, то на каком-то уровне, скорее всего, либо:

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

Если нет аппаратной поддержки, то компилятор, вероятно, использует последний метод, испуская только код ассемблера (без отладочных символов), а не используя библиотеку c-что затрудняет отслеживание фактического кода в отладчике.


Если вы хотите посмотреть на фактическую реализацию GNU этих функций в C, проверьте последнюю магистраль glibc. Вижу библиотека GNU C.


вычисление синуса / Косинуса / касательной на самом деле очень легко сделать с помощью кода с использованием серии Тейлора. Написание одного из них занимает около 5 секунд.

весь процесс можно обобщить с помощью этого уравнения здесь: http://upload.wikimedia.org/math/5/4/6/546ecab719ce73dfb34a7496c942972b.png

вот некоторые процедуры, которые я написал для C:

double _pow(double a, double b) {
    double c = 1;
    for (int i=0; i<b; i++)
        c *= a;
    return c;
}

double _fact(double x) {
    double ret = 1;
    for (int i=1; i<=x; i++) 
        ret *= i;
    return ret;
}

double _sin(double x) {
    double y = x;
    double s = -1;
    for (int i=3; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _cos(double x) {
    double y = 1;
    double s = -1;
    for (int i=2; i<=100; i+=2) {
        y+=s*(_pow(x,i)/_fact(i));
        s *= -1;
    }  
    return y;
}
double _tan(double x) {
     return (_sin(x)/_cos(x));  
}

Не используйте серию Тейлора. Полиномы Чебышева являются как более быстрыми, так и более точными, как указывалось несколькими людьми выше. Вот реализация (первоначально из ZX Spectrum ROM):https://albertveli.wordpress.com/2015/01/10/zx-sine/


Если вы хотите грешить, то asm volatile("fsin": "=t"(vsin) : "0"(xrads)); если вы хотите, потому что asm volatile("fcos": "=t"(vcos) : "0"(xrads)); если вы хотите корень после asm volatile("fsqrt": "=t"(vsqrt) : "0" (значение)); так зачем использовать неточный код, когда инструкции машины будут делать.