Создание пользовательской синусоидальной функции

Я попытался создать пользовательскую функцию sine, используя c и Taylor Series для вычисления sin с 10 элементами в ряду, но я получаю неверные результаты, когда пытаюсь найти sine(x), где x > 6.

Это хорошо работает для -5 < x < 5, но что-либо вне этого диапазона не дает правильных результатов.

Я ожидаю, что sin(10) вернет что-то близкое к -0.5440, но получите 1418.0269775391

Я поместил все в один файл, чтобы было проще.

#include <stdio.h>
#include <stdlib.h>

double factorial(double n);
double power(double n, double pow);
double sine(double n);

// This is supposed to all go in a .c file and reference the .h stuff above
// This is the actual implementation of the functions declared above
double factorial(double n) {
    // 0! = 1 so just return it
    if(n == 0) {
        return 1;
    }
    // Recursively call factorial with n-1 until n == 0
    return n * (factorial(n - 1)); 
}


double power(double n, double power) {
    double result = n;
    // Loop as many times as the power and just multiply itself power amount of times
    for(int i = 1; i < power; i++) {
        result = n * result;
    }
    return result;
}

double sine(double n) {
    double result = n;
    double coefficent = 3; // Increment this by 2 each loop
    for(int i = 0; i < 10; i++) { // Change 10 to go out to more/less terms
        double pow = power(n, coefficent);
        double frac = factorial(coefficent);
        printf("Loop %d:\n%2.3f ^ %2.3f = %2.3f\n", i, n, coefficent, pow);
        printf("%2.3f! = %2.3f\n", coefficent, frac);

        // Switch between adding/subtracting
        if(i % 2 == 0) { // If the index of the loop is divided by 2, the index is even, so subtract
            result = result - (pow/frac); // x - ((x^3)/(3!)) - ((x^5)/(5!))...
        } else {
            result = result + (pow/frac); // x - ((x^3)/(3!)) + ((x^5)/(5!))...
        }
        coefficent = coefficent + 2;
        printf("Result = %2.3f\n\n", result);
    }
    return result;
}

// main starting point. This is suppossed to #include "functions.c" which contain the above functions in it
int main(int argc, char** argv) {

    double number = atof(argv[1]); // argv[1] = "6"
    double sineResult = sine(number);
    printf("%1.10f", sineResult);
    return (0);
}

person Jimenemex    schedule 13.02.2018    source источник
comment
Если вы еще не пробовали это раньше, самое время научитесь отлаживать наши программы.   -  person Some programmer dude    schedule 13.02.2018
comment
И почему вы делаете double number = atof("5")? Почему бы просто не double number = 5.0? Или просто позвонить sine(5.0)? Или, поскольку функция принимает аргумент int, int number = 5; или sine(5)?   -  person Some programmer dude    schedule 13.02.2018
comment
@Someprogrammerdude Я отладил его, и он делает то, что ожидается от sin(1) до sin(5). У меня нет другой причины, почему это не сработает для любого другого sin(x). Я также использую atof("5") вместо atof(argv[1]).   -  person Jimenemex    schedule 13.02.2018
comment
Вы используете int во многих местах. В конце концов, один из них переполнится. Например, power(10, 10) не вернет правильный ответ (поскольку правильный ответ — 10 миллиардов, что не помещается в 32-битное int).   -  person user3386109    schedule 13.02.2018
comment
Редактирование float не сильно помогает. По крайней мере, используйте double везде.   -  person user3386109    schedule 13.02.2018
comment
Итак, теперь вопрос: какова ценность 10-го члена. Обратите внимание, что по мере того, как x становится больше, числитель каждого члена растет быстрее, а это означает, что вам нужно больше членов, чтобы знаменатель наверстал упущенное. Лучшее решение — вычитать или прибавлять 2π по мере необходимости, пока x не окажется между -π и π. Таким образом, вам нужно только достаточно терминов, чтобы получить точные результаты для небольших значений x.   -  person user3386109    schedule 13.02.2018
comment
В sine(n) сначала используйте n = fmod(n, 2*pi); или эквивалент.   -  person chux - Reinstate Monica    schedule 13.02.2018
comment
у вас остался один int в power, измените его на double и sine(6) появится как -0.279, что достаточно близко к более точному результату -0.27941549819892587281.   -  person deamentiaemundi    schedule 13.02.2018
comment
Уменьшение диапазона качества в радианах — более сложная задача, чем вычисление sine(). См. https://www.csee.umbc.edu/~phatak/645/supl/Ng-ArgReduction.pdf   -  person chux - Reinstate Monica    schedule 13.02.2018
comment
@chux Я пытаюсь сделать это, не используя никаких других ресурсов.   -  person Jimenemex    schedule 13.02.2018
comment
@deamentiaemundi Итак, моя логика была верна, это просто было связано с точностью использования double вместо int и float?   -  person Jimenemex    schedule 13.02.2018
comment
@JustinJmnz Сначала используйте n = fmod(n, 2*pi);, чтобы увидеть, решит ли это проблемы. Затем найдите идеи сокращения без использования каких-либо других ресурсов.   -  person chux - Reinstate Monica    schedule 13.02.2018
comment
'синус (х)'? Что представляет собой «х»? градусы, радианы, квадранты прицеливания, что?   -  person user3629249    schedule 13.02.2018
comment
@ user3629249 В контексте ряда Тейлора это радианы.   -  person user3386109    schedule 13.02.2018
comment
относительно: int main(int argc, char** argv) { Ни один из параметров не используется, поэтому либо первые две строки в теле main() должны быть: (void)argc; и (void)argv;, либо подпись должна быть: int main( void )   -  person user3629249    schedule 13.02.2018
comment
относительно: функция: double power(double n, double power) { рабочее значение равно int ((int result = n;)) и возвращаемое значение равно int ((возвращенный результат;)) Это не согласуется ни с логикой функции, ни с сигнатурой функция, в которой указано, что возвращаемый тип будет double   -  person user3629249    schedule 13.02.2018
comment
относительно: double coefficent = 3; эта переменная объявляется как double, так зачем передавать ей int? Предложить: double coefficent = 3.0; Аналогично для coefficent = coefficent + 2; Предложить: coefficent = coefficent + 2.0;   -  person user3629249    schedule 13.02.2018


Ответы (3)


Как я уже говорил в Python : вычисление синуса/косинуса с точностью до 1 миллиона знаков

Реальное расширение Тейлора с центром в x0:

где Rn — остаток Лагранжа.

Обратите внимание, что Rn быстро растет, как только x удаляется от центра x0.

Поскольку вы реализуете ряд Маклорена (ряд Тейлора с центром в 0), а не общий ряд Тейлора, ваша функция будет давать действительно неправильные результаты при попытке вычислить sin (x) для больших значений x.

Таким образом, перед циклом for в функции sine() вы должны уменьшить домен как минимум до [-pi, pi]... лучше, если вы уменьшите его до [0, pi] и воспользоваться четностью синусов.

Чтобы исправить свой код, вам понадобится fmod() из math.h, поэтому вы можете сделать следующее:

#include <math.h>

// Your code

double sine (double n) {
    // Define PI
    const double my_pi = 3.14159265358979323846;
    // Sine's period is 2*PI
    n = fmod(n, 2 * my_pi);
    // Any negative angle can be brought back
    // to it's equivalent positive angle
    if (n < 0) {
        n = 2 * my_pi - n;
    }
    // Sine is an odd function...
    // let's take advantage of it.
    char sign = 1;
    if (n > my_pi) {
        n -= my_pi;
        sign = -1;
    }
    // Now n is in range [0, PI].

    // The rest of your function is fine

    return sign * result;
}

Теперь, если вы действительно ненавидите модуль math.h, вы можете реализовать свой собственный модуль fmod() вот так:

double fmod(double a, double b)
{
    double frac = a / b;
    int floor = frac > 0 ? (int)frac : (int)(frac - 0.9999999999999999);
    return (a - b * floor);
}

Try it online!

person Marco    schedule 13.02.2018

после внесения исправлений, перечисленных в моих комментариях к вопросу, предлагаемый код выглядит так:

#include <stdio.h>
#include <stdlib.h>

double factorial(double n);
double power(double n, double pow);
double sine(double n);

// This is supposed to all go in a .c file and reference the .h stuff above
// This is the actual implementation of the functions declared above
double factorial(double n) {
    // 0! = 1 so just return it
    if(n == 0) {
        return 1;
    }
    // Recursively call factorial with n-1 until n == 0
    return n * (factorial(n - 1));
}


double power(double n, double power) {
    double result = n;
    // Loop as many times as the power and just multiply itself power amount of times
    for(int i = 1; i < power; i++) {
        result = n * result;
    }
    return result;
}

double sine(double n) {
    double result = n;
    double coefficent = 3.0; // Increment this by 2 each loop

    for(int i = 0; i < 10; i++) { // Change 10 to go out to more/less terms
        double pow = power(n, coefficent);
        double frac = factorial(coefficent);
        printf("Loop %d:\n%2.3f ^ %2.3f = %2.3f\n", i, n, coefficent, pow);
        printf("%2.3f! = %2.3f\n", coefficent, frac);

        // Switch between adding/subtracting
        if(i % 2 == 0) { // If the index of the loop is divided by 2, the index is even, so subtract
            result = result - (pow/frac); // x - ((x^3)/(3!)) - ((x^5)/(5!))...
        } else {
            result = result + (pow/frac); // x - ((x^3)/(3!)) + ((x^5)/(5!))...
        }
        coefficent = coefficent + 2;
        printf("Result = %2.3f\n\n", result);
    }
    return result;
}


// main starting point. This is suppossed to #include "functions.c" which contain the above functions in it
int main( void )
{
    double number = atof("6");
    double sineResult = sine(number);
    printf("%1.10f", sineResult);
    return (0);
}

и результирующий вывод выглядит так:

Loop 0:
6.000 ^ 3.000 = 216.000
3.000! = 6.000
Result = -30.000

Loop 1:
6.000 ^ 5.000 = 7776.000
5.000! = 120.000
Result = 34.800

Loop 2:
6.000 ^ 7.000 = 279936.000
7.000! = 5040.000
Result = -20.743

Loop 3:
6.000 ^ 9.000 = 10077696.000
9.000! = 362880.000
Result = 7.029

Loop 4:
6.000 ^ 11.000 = 362797056.000
11.000! = 39916800.000
Result = -2.060

Loop 5:
6.000 ^ 13.000 = 13060694016.000
13.000! = 6227020800.000
Result = 0.037

Loop 6:
6.000 ^ 15.000 = 470184984576.000
15.000! = 1307674368000.000
Result = -0.322

Loop 7:
6.000 ^ 17.000 = 16926659444736.000
17.000! = 355687428096000.000
Result = -0.275

Loop 8:
6.000 ^ 19.000 = 609359740010496.000
19.000! = 121645100408832000.000
Result = -0.280

Loop 9:
6.000 ^ 21.000 = 21936950640377856.000
21.000! = 51090942171709440000.000
Result = -0.279

-0.2793866930
person user3629249    schedule 13.02.2018

Расширение Тейлора имеет ошибку, которая зависит от области действия аргумента, а также от порядка расширения Тейлора. Я считаю, что вы перешли границы спора. Дополнительные примеры см. здесь: www.dotancohen.com/eng/taylor-sine.php

person VladP    schedule 13.02.2018