Як C обчислює sin () та інші математичні функції?


248

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

Хтось може допомогти мені їх знайти? Я відчуваю, що навряд чи ВСЕ апаратне забезпечення, яке буде працювати на C, підтримує тригерні функції в апараті, тож десь має бути програмний алгоритм , правда?


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


2
Зауважте, що ця реалізація залежить. Ви повинні вказати, яка реалізація вас найбільше цікавить.
jason

3
Я помітив .NET і C, тому що я шукав в обох місцях і не міг зрозуміти. Хоча дивлячись на розбирання .NET, схоже, це може бути виклик некерованого C, тому, наскільки я знаю, вони мають ту саму реалізацію.
Хенк

Відповіді:


213

У GNU libm реалізація sinзалежить від системи. Тому ви можете знайти реалізацію для кожної платформи десь у відповідному підкаталозі sysdeps .

Один каталог включає в себе реалізацію на C, внесену IBM. З жовтня 2011 року це код, який фактично працює під час виклику sin()в типовій системі Linux x86-64. Це, мабуть, швидше, ніж fsinінструкція по збірці. Вихідний код: sysdeps / ieee754 / dbl-64 / s_sin.c , шукайте __sin (double x).

Цей код дуже складний. Жоден програмний алгоритм не є максимально швидким, а також точним у всьому діапазоні значень x , тому бібліотека реалізує кілька різних алгоритмів, і її перше завдання полягає в тому, щоб переглянути х і вирішити, який алгоритм використовувати.

  • Коли х дуже дуже близько до 0, sin(x) == xце правильна відповідь.

  • Трохи далі sin(x)використовує знайомий ряд Тейлора. Однак це лише близько 0, тож ...

  • Коли кут більше приблизно 7 °, використовується інший алгоритм, обчислюючи наближення ряду Тейлора як для sin (x), так і cos (x), а потім використовуючи значення з попередньо обчисленої таблиці для уточнення наближення.

  • Коли | х | > 2, жоден з перерахованих вище алгоритмів не працюватиме, тому код починається з обчислення деякого значення, ближчого до 0, яке можна подавати sinабо cosзамість нього.

  • Є ще одна галузь, яка має справу з тим, що x - це NaN або нескінченність.

Цей код використовує численні хакі, яких я ніколи не бачив, хоча, наскільки я знаю, вони можуть бути добре відомими серед експертів з плаваючою комою. Іноді для декількох рядків коду потрібно пояснити кілька абзаців. Наприклад, ці два рядки

double t = (x * hpinv + toint);
double xn = t - toint;

використовуються (іноді) для зменшення x до значення, близького до 0, яке відрізняється від x кратним π / 2, конкретно xn× π / 2. Те, як це зробити без поділу чи розгалуження, досить розумне. Але коментарів взагалі немає!


Старіші 32-розрядні версії GCC / glibc використовували fsinінструкцію, яка дивно неточна для деяких входів. Існує захоплююча публікація в блозі, що ілюструє це лише двома рядками коду .

Реалізація fdlibm sinу чистому С набагато простіша, ніж у glibc, і це добре коментується. Вихідний код: fdlibm / s_sin.c та fdlibm / k_sin.c


35
Щоб побачити, що це дійсно код, який працює на x86: складіть програму, яка викликає sin(); введіть gdb a.out, то break sin, то run, то disassemble.
Джейсон Орендорф

5
@ Генрі: не робіть помилки, думаючи, що це хороший код. Це дійсно страшно , не вчіться кодувати таким чином!
Томас Боніні

2
@Andreas Хм, ви праві, код IBM виглядає дуже жахливо порівняно з fdlibm. Я відредагував відповідь, щоб додати посилання на звичайну функцію fdlibm.
Джейсон Орендорф

3
@Henry: __kernel_sinвизначається в k_sin.c, однак це і є чисто. C. Клацніть його ще раз - я вперше отримав URL-адресу.
Jason Orendorff

3
Зв'язаний код sysdeps особливо цікавий тим, що він правильно закруглений. Тобто, це, мабуть, дає найкращу відповідь на всі вхідні значення, що стало можливим лише нещодавно. У деяких випадках це може бути повільним, оскільки для правильного округлення може знадобитися обчислити багато зайвих цифр. В інших випадках це надзвичайно швидко - на досить малі числа відповідь - це лише кут.
Брюс Доусон

66

Такі функції, як синус і косинус, реалізуються в мікрокодексі всередині мікропроцесорів. Наприклад, для мікросхем Intel є інструкція по збірці. Компілятор змінного струму генерує код, який викликає ці інструкції по збірці. (Навпаки, компілятор Java не буде. Java оцінює триггерні функції в програмному, а не апаратному забезпеченні, і тому він працює набагато повільніше.)

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


10
Трансцендентальні математичні функції, такі як синус і косинус, можуть бути реалізовані в мікрокодеках або в якості інструкцій з обладнання в поточних 32-бітних процесорах настільних та серверів. Це було не завжди, поки в i486 (DX) всі розрахунки з плаваючою точкою не проводилися в програмному забезпеченні ("soft-float") для серії x86 без окремого співпроцесора. Не всі (FPU) включали трансцендентні функції (наприклад, Weitek 3167).
mctylr

1
Чи можете ви бути більш конкретними? Як можна "відшліфувати" наближення за допомогою серії Тейлора?
Хенк

4
Що стосується "полірування" відповіді, припустимо, ви обчислюєте як синус, так і косинус. Припустимо, ви знаєте точне значення обох в одному пункті (наприклад, від CORDIC), але хочете значення в сусідній точці. Тоді для невеликої різниці h можна застосувати наближення Тейлора f (x + h) = f (x) + h f '(x) або f (x + h) = f (x) + h f' (x) + h ^ 2 f '' (x) / 2.
Джон Д. Кук

6
На чіпах x86 / x64 є інструкція по збірці для обчислення синусу (fsin), але ця інструкція іноді досить неточна і тому рідко вже використовується. Докладніше дивіться у randomascii.wordpress.com/2014/10/09/… . Більшість інших процесорів не мають інструкцій щодо синусів і косинусів, тому що обчислення їх у програмному забезпеченні дає більшу гнучкість і може бути навіть швидше.
Брюс Доусон

3
Шнуровий матеріал всередині інтелектуальних мікросхем, як правило, НЕ використовується. По-перше, точність та роздільна здатність операції надзвичайно важливі для багатьох застосувань. Кордик, як відомо, неточний, коли ви добираєтесь до 7-ої цифри або близько того, і непередбачуваний. По-друге, я чув, що є помилка в їх реалізації, що спричиняє ще більше проблем. Я поглянув на функцію sin для linux gcc, і напевно, він використовує чебишев. вбудований матеріал не використовується. Так само, алгоритм шнура в мікросхемі повільніше, ніж програмне рішення.
Дональд Мюррей

63

Добре дітки, час для плюсів .... Це одна з моїх найбільших скарг на недосвідчених програмних інженерів. Вони приходять в обчисленні трансцендентальних функцій з нуля (використовуючи серію Тейлора) так, ніби ніхто ніколи в житті не робив цих розрахунків. Неправда. Це чітко визначена проблема, до якої тисячі разів звертаються дуже розумні інженери програмного забезпечення та апаратних засобів і має чітко визначене рішення. В основному більшість трансцендентальних функцій для їх обчислення використовують поліноси Чебишева. Що стосується того, які поліноми використовуються, залежить від обставин. По-перше, біблія з цього приводу - це книга Харт і Чейні під назвою "Комп'ютерні наближення". У цій книзі ви можете вирішити, чи є у вас апаратний суматор, множник, дільник тощо, і вирішити, які операції найшвидші. Наприклад, якщо у вас був дійсно швидкий дільник, найшвидший спосіб обчислити синус може бути 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 (вони знаходяться у плаваючій точці). Потім ви реалізуєте свій алгоритм у програмному забезпеченні у вигляді: (((p4.x + p3) .x + p2) .x + p1) .x + p0 .... і ось як би ви обчислили косинус до 7 десятків місця на цьому обладнання.

Зауважте, що більшість апаратних реалізацій трансцендентальних операцій у FPU зазвичай включають деякі мікрокоди та такі операції (залежить від обладнання). Поліноми Чебишева використовуються для більшості трансцендентальних, але не для всіх. наприклад, квадратний корінь швидше використовувати подвійну ітерацію методу Ньютона Рафсона, використовуючи спочатку таблицю пошуку. Знову ж, ця книга "Комп'ютерні наближення" скаже вам це.

Якщо ви плануєте виконувати ці функції, я б рекомендував усім отримати копію цієї книги. Це дійсно є біблією для таких видів алгоритмів. Зауважте, що існують групи альтернативних засобів для обчислення таких значень, як шнури та ін., Але вони, як правило, найкращі для конкретних алгоритмів, де вам потрібна лише низька точність. Щоб гарантувати точність кожного разу, поліноми чебишева - це шлях. Як я вже сказав, чітко визначена проблема. Вирішується вже 50 років ..... і ось як це робиться.

Тепер, як сказано, існують методи, за допомогою яких поліноми Чебишева можна використовувати для отримання єдиного результату точності з поліномом низького ступеня (як приклад для косинуса вище). Потім існують інші методи інтерполяції між величинами, щоб збільшити точність без необхідності переходити до значно більшого полінома, наприклад, "Метод точних таблиць Гал". Ця остання методика - це те, про що йдеться у публікації, що посилається на літературу ACM. Але врешті-решт, поліноми Чебишева - це те, що використовується, щоб пройти 90% шляху туди.

Насолоджуйтесь.


6
Я не міг більше погодитися з першими пропозиціями. Також варто нагадати, що обчислення спеціальних функцій із гарантованою точністю є важкою проблемою . Розумні люди, яких ви згадуєте, проводять більшу частину свого життя, роблячи це. Крім того, на більш технічну ноту, поліноми min-max є затребуваним зерном, а поліноми Чебишева для них простішими проксі.
Олександр К.

161
-1 за непрофесійний і химерний (і м'яко грубий) тон, а також за те, що фактичний непотрібний зміст цієї відповіді, позбавленої розгулу та поблажливості, в основному зводиться до "Вони часто використовують поліноми Чебишева; дивіться цю книгу для більш детальної інформації, це дійсно добре! " Це, ви знаєте, може бути абсолютно правильним, але насправді це не такий самодостатній відповідь, якого ми хочемо тут, натомість. Згублений таким чином, він би гідно прокоментував це питання.
Ільмарі Каронен

2
Ще в перші роки розвитку гри зазвичай це робилося за допомогою таблиць пошуку критичної потреби в швидкості). Зазвичай для цих речей ми не використовували стандартні функції lib.
Топспін

4
Я використовую таблиці пошуку у вбудованих системах досить часто і біттіанів (замість радіанів), але це для спеціалізованого додатка (як ваші ігри). Я думаю, хлопця цікавить, як компілятор c обчислює гріх для чисел з плаваючою комою ....
Дональд Мюррей

1
Ах, 50 років тому. Я почав грати з такими на Burroughs B220 із серії McLaren. Пізніше апаратне забезпечення CDC, а потім Motorola 68000. Arcsin був безладним - я вибрав коефіцієнт двох многочленів і розробив код, щоб знайти оптимальні коефіцієнти.
Рік Джеймс

15

Для sinконкретно, використовуючи розкладання в ряд Тейлора дасть вам:

sin (x): = x - x ^ 3/3! + х ^ 5/5! - х ^ 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) працює через гріх апроксимації (x) = x для малих кутів. Для більших кутів потрібно обчислювати все більше і більше термінів, щоб отримати прийнятні результати. Ви можете використовувати аргумент час і продовжувати певну точність:

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;

}

1
Якщо трохи змінити коефіцієнти (і важко кодувати їх у многочлен), ви можете зупинитись приблизно на 2 ітерації раніше.
Рік Джеймс

14

Так, існують і програмні алгоритми для розрахунку sin. В основному, обчислення такого роду матеріалів за допомогою цифрового комп'ютера, як правило, здійснюється за допомогою числових методів, таких як наближення серії Тейлора, що представляє функцію.

Числові методи можуть наближати функції до довільної величини точності, і оскільки кількість точності, яку ви маєте у плаваючому числі, є кінцевим, вони досить добре відповідають цим завданням.


12
Реальна реалізація, ймовірно, не використовуватиме серію Тейлора, оскільки є більш ефективні способи. Вам потрібно лише правильно наблизити домен [0 ... pi / 2], і є функції, які дозволять забезпечити гарне наближення ефективніше, ніж серії Тейлора.
Девід Торнлі

2
@David: Я згоден. Я був досить обережним, щоб згадати слово "як" у своїй відповіді. Але розширення Тейлора є простим для пояснення ідеї методів, що наближають функції. При цьому я бачив реалізацію програмного забезпечення (не впевнений, чи були вони оптимізовані), які використовували серію Taylor.
Мехрдад Афшарі

1
Власне, поліномічні наближення є одним із найефективніших способів обчислення тригонометричних функцій.
Джеремі Салвен

13

Скористайтеся серіями Тейлора і спробуйте знайти співвідношення між умовами серії, щоб ви не розраховували речі знову і знову

Ось приклад косинуса:

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 )

пояснення


2
Чи знаєте ви, що ви можете використовувати API Chart Google для створення таких формул за допомогою TeX? code.google.com/apis/chart/docs/gallery/formulas.html
Габ Ройер

11

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

Є кілька таких реалізацій там. Один є у fdlibm і використовується в Java. Наскільки мені відомо, реалізація glibc містить частини fdlibm та інші частини, внесені IBM.

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


3
Реєстри SSE2 не використовуються для обчислення sin (), ні в режимі x86, ні в режимі x64, і, звичайно, sin обчислюється апаратно незалежно від режиму. Гей, ми живемо у 2010 році :)
Ігор Корхов

7
@Igor: це залежить від того, яку математичну бібліотеку ви шукаєте. Виявляється, найбільш оптимізовані математичні бібліотеки на x86 використовують реалізацію програмного забезпечення SSE для, sinі cosце швидше, ніж інструкції з обладнання на FPU. Простіші, наївніші бібліотеки, як правило, використовують інструкції fsinта fcosінструкції.
Стівен Канон

@Stephen Canon: Чи мають у цих швидких бібліотек точність 80 біт, як це роблять регістри FPU? У мене є дуже підлі підозри, що вони віддають перевагу швидкості над точністю, що, звичайно, є розумним у багатьох сценаріях, наприклад, в іграх. І я вважаю, що обчислення синуса з 32-бітовою точністю за допомогою SSE та попередньо обчислених проміжних таблиць може бути швидше, ніж FSINз повною точністю. Я був би дуже вдячний, якщо ви скажете мені назви цих швидких бібліотек, цікаво подивитися.
Ігор Корхов

@Igor: на x86 у 64-бітному режимі, принаймні, на всіх системах, схожих на Unix, про які я знаю, точність обмежена 64 бітами, а не 79 бітами FPU x87. Реалізація програмного забезпечення sin()відбувається вдвічі швидше, ніж fsinобчислюється (саме тому, що це робиться з меншою точністю). Зауважте, що x87, як відомо, має трохи меншу фактичну точність, ніж оголошені 79 біт.
Томас Порнін

1
Дійсно, і 32-бітні, і 64-бітні реалізації sin () у бібліотеках виконання msvc не використовують інструкцію FSIN. Насправді вони дають різні результати, візьмемо для прикладу гріх (0.70444454416678126). Це призведе до отримання 0,64761068800896837 (правильного відхилення 0,5 * (eps / 2) допуску) в 32-бітній програмі, а в 64-бітовій - 0,64761068800896848 (неправильно).
e.tadeu

9

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

У деяких випадках максимальна помилка - це не те, що вас цікавить, а максимальна відносна помилка. Наприклад, для синусоїдичної помилки помилка біля x = 0 повинна бути значно меншою, ніж для більших значень; ви хочете невелику відносну помилку. Отже, ви обчислили б поліном Чебишева на sin x / x і помножили цей многочлен на x.

Далі ви повинні розібратися, як оцінити поліном. Ви хочете оцінити його таким чином, щоб проміжні значення були малі, а отже, помилки округлення невеликі. Інакше помилки округлення можуть стати набагато більшими, ніж помилки в многочлени. І з такими функціями, як синусова функція, якщо ви недбалі, можливо, результат, який ви обчислюєте для sin x, більший, ніж результат для sin y, навіть коли x <y. Тому потрібен ретельний вибір порядку розрахунку та обчислення верхніх меж для помилки округлення.

Наприклад, sin x = x - x ^ 3/6 + x ^ 5/120 - x ^ 7/5040 ... Якщо розрахувати наївно sin x = x * (1 - x ^ 2/6 + x ^ 4 / 120 - х ^ 6/5040 ...), то ця функція в дужках зменшується, і це буде відбуватися , що якщо у наступного більшого числа х, то іноді грішать у буде менше , ніж гріх х. Натомість обчисліть 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) з многочленом (bx ^ 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 разів від останнього біта.


1
Це гарне пояснення того , як один може обчислити гріх (х) ефективно, але це на самому ділі не здається , відповісти на питання OP, який є конкретно про те , як загальні бібліотеки C / Укладачів цього розрахунку.
Ільмарі Каронен

Поліноми Чебишева мінімізують максимальне абсолютне значення протягом інтервалу, але вони не мінімізують найбільшу різницю між цільовою функцією та поліномом. Поліноми Minimax роблять це.
Eric Postpischil

9

Що стосується тригонометричних функцій , як 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+ інший) і забезпечує алгоритм рішення дає якісні результати для всіх double від -DBL_MAXдо DBL_MAX.


Якщо вихідні аргументи є в градусах, але вони можуть мати велике значення, fmod()спочатку скористайтеся для підвищення точності. Товар fmod()не введе помилок і таким чином забезпечить чудове скорочення дальності.

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

Різні ідентичності тригерів і remquo()пропонують ще більше вдосконалення. Зразок: sind ()


6

Фактична реалізація функцій бібліотеки залежить від конкретного компілятора та / або постачальника бібліотеки. Незалежно від того, чи це зроблено в апаратному чи програмному забезпеченні, чи це розширення Тейлора, чи ні, і т.д., буде відрізнятися.

Я розумію, що це абсолютно не допоможе.


5

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

Зауважте, що ці програмні підпрограми не є частиною джерел компілятора, а скоріше їх можна знайти в бібліотеці correspoding, таких як clib або glibc для компілятора GNU. Див. Http://www.gnu.org/software/libc/manual/html_mono/libc.html#Trig-Functions

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


5

Якщо ви хочете реалізувати програмне забезпечення, а не апаратне забезпечення, місце для пошуку остаточної відповіді на це питання - глава 5 числових рецептів . Моя копія знаходиться у вікні, тому я не можу наводити деталей, але коротка версія (якщо я пам’ятаю це право) полягає в тому, що ви приймаєте tan(theta/2)за свою примітивну операцію і обчислюєте звідти інших. Обчислення проводиться з наближенням серії, але це щось, що зближується набагато швидше, ніж серія Тейлора.

Вибачте, я не можу більше згадати, не подавши руку на книгу.


5

Немає нічого схожого на те, щоб потрапити на джерело і побачити, як хтось насправді зробив це в загальній бібліотеці; розглянемо, зокрема, одну реалізацію бібліотеки С. Я вибрав uLibC.

Ось функція гріха:

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


4

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

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

Якщо апаратної підтримки немає, тоді компілятор, ймовірно, використовує останній метод, випромінюючи лише код асемблера (без символів налагодження), а не використовуючи бібліотеку змінного струму ---, що ускладнює вам відстеження фактичного коду у вашому налагоджувачі.


4

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

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

Ви також можете подивитися на файли з .tblрозширенням, їх вміст - це не що інше, як величезні таблиці попередньо обчислених значень різних функцій у двійковій формі. Ось чому реалізація настільки швидка: замість обчислення всіх коефіцієнтів будь-яких серій вони використовують, вони просто роблять швидкий пошук, що набагато швидше. До речі, вони використовують серію Tailor для обчислення синусів та косинусів.

Я сподіваюся, що це допомагає.


4

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

У мові С стандартна бібліотека С включає загальні математичні функції, не включені до самої мови (наприклад pow, sinі cosдля енергії, синуса та косинуса відповідно). Заголовки яких включені до математики .

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

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

Тепер компілятор може оптимізувати стандартну функцію бібліотеки C sin()(надається libm.so), яку слід замінити викликом до рідної інструкції до вбудованої функції sin () вашого CPU / FPU, яка існує як інструкція FPU ( FSINдля x86 / x87) на новіші процесори, як серія Core 2 (це правильно, як і в i486DX). Це залежало б від прапорів оптимізації, переданих компілятору gcc. Якби компілятору було сказано написати код, який би виконувався на будь-якому i386 або новішому процесорі, він не робив такої оптимізації. -mcpu=486Прапор повідомить компілятору , що це було безпечно , щоб зробити таку оптимізацію.

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

Будь-яка нещодавня (приблизно 2,9х прим.) Версія gcc також пропонує вбудовану версію sin, __builtin_sin()яка буде використовуватися для заміни стандартного виклику на версію бібліотеки C, як оптимізацію.

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



3

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


2
Схоже, це не відповідає на запитання. OP запитує , як Тріг функція буде обчислена загальними компілятори C / бібліотеками (і я впевнений , що ZX Spectrum не відповідає), а не як вони повинні бути розраховані. Це, мабуть, було б корисним коментарем щодо деяких попередніх відповідей.
Ільмарі Каронен

1
Ах, ти маєш рацію. Це мав бути коментар, а не відповідь. Я не використовував SO деякий час і забув, як працює система. У будь-якому разі, я думаю, що впровадження Spectrum є актуальним, оскільки у нього був дуже повільний процесор, а швидкість була суттєвою. Тоді найкращий алгоритм, безумовно, все ще досить хороший, тому було б гарною ідеєю, щоб бібліотеки С реалізували тригічні функції за допомогою поліномів Чебишева.
Альберт Велі

2

Обчислити синус / косинус / тангенс насправді дуже просто за допомогою коду за допомогою серії Тейлора. Написання одного займає приблизно 5 секунд.

Весь процес можна підсумувати цим рівнянням тут:

гріх і розширення витрат

Ось декілька процедур, які я написав для 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));  
}

4
Це досить погана реалізація, оскільки не використовується те, що послідовні умови сінусоїдальних і косинусних серій мають дуже прості коефіцієнти. Що означає, що можна зменшити кількість множень і ділень з O (n ^ 2) тут до O (n). Подальше зменшення досягається вдвічі та квадратуванням, як, наприклад, це робиться в математичній бібліотеці bc (POSIX мультиточний калькулятор).
Лутц Леманн

2
Здається, він також не відповідає на запитання; ОП запитує, як обчислюються функції тригерів звичайними компіляторами / бібліотеками, а не для користувацького повторного здійснення.
Ільмарі Каронен

2
Я думаю, що це хороша відповідь, оскільки відповідає духу питання, яке (і я можу лише здогадуватися, звичайно) цікавість щодо інакше «чорної скриньки» функціонує як sin (). Тут єдина відповідь, яка дає можливість швидше зрозуміти, що відбувається, проглянувши його за кілька секунд, а не прочитавши оптимізований вихідний код С.
Майк М

насправді бібліотеки використовують набагато більш оптимізовану версію, розуміючи, що після того, як у вас є термін, ви можете отримати наступний термін шляхом множення деяких значень. Дивіться приклад у відповіді Блінді . Ви підраховуєте потужність і заводи знову і знову, що набагато повільніше
phuclv


0

Удосконалена версія коду з відповіді Блінді

#define EPSILON .0000000000001
// this is smallest effective threshold, at least on my OS (WSL ubuntu 18)
// possibly because factorial part turns 0 at some point
// and it happens faster then series element turns 0;
// validation was made against sin() from <math.h>
double ft_sin(double x)
{
    int k = 2;
    double r = x;
    double acc = 1;
    double den = 1;
    double num = x;

//  precision drops rapidly when x is not close to 0
//  so move x to 0 as close as possible
    while (x > PI)
        x -= PI;
    while (x < -PI)
        x += PI;
    if (x > PI / 2)
        return (ft_sin(PI - x));
    if (x < -PI / 2)
        return (ft_sin(-PI - x));
//  not using fabs for performance reasons
    while (acc > EPSILON || acc < -EPSILON)
    {
        num *= -x * x;
        den *= k * (k + 1);
        acc = num / den;
        r += acc;
        k += 2;
    }
    return (r);
}

0

Суть того, як це робиться, полягає у цьому уривку з прикладного чисельного аналізу Джеральда Вітлі:

Коли ваша програма вимагає від комп'ютера отримати значення введіть тут опис зображенняабо введіть тут опис зображенняви задумалися, як він може отримати значення, якщо найпотужніші функції, які він може обчислити, - це поліноми? Це не виглядає в таблицях та інтерполювати! Швидше за все, комп'ютер наближає кожну функцію, окрім поліномів, з якогось многочлена, призначеного для того, щоб задавати значення дуже точно.

Кілька моментів, які слід зазначити у вищесказаному, полягає в тому, що деякі алгоритми роблять інфактний інтерполяцію з таблиці, хоча і лише для перших кількох ітерацій. Також зауважте, як згадується, що комп'ютери використовують наближені многочлени, не вказуючи, який тип наближеного многочлена. Як вказували інші в нитці, поліноми Чебишева в цьому випадку ефективніші, ніж поліноми Тейлора.


-1

якщо ви хочете sinтоді

 __asm__ __volatile__("fsin" : "=t"(vsin) : "0"(xrads));

якщо ви хочете cosтоді

 __asm__ __volatile__("fcos" : "=t"(vcos) : "0"(xrads));

якщо ви хочете sqrtтоді

 __asm__ __volatile__("fsqrt" : "=t"(vsqrt) : "0"(value));

так навіщо використовувати неточний код, коли виконуватимуться інструкції на машині?


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.