Ряд Тейлора в Си (проблема с грехом (240) и грехом (300)) - PullRequest
1 голос
/ 23 октября 2019
#include <stdio.h>
#include <math.h>

const int TERMS = 7;
const float PI =  3.14159265358979;

int fact(int n) {
    return n<= 0 ? 1 : n * fact(n-1);
}


double sine(int x) {
    double rad = x * (PI / 180);
    double sin = 0;


    int n;
    for(n = 0; n < TERMS; n++) { // That's Taylor series!!
        sin += pow(-1, n) * pow(rad, (2 * n) + 1)/ fact((2 * n) + 1);
    }
    return sin;
}


double cosine(int x) {
    double rad = x * (PI / 180);
    double cos = 0;

    int n;

    for(n = 0; n < TERMS; n++) { // That's also Taylor series!
         cos += pow(-1, n) * pow(rad, 2 * n) / fact(2 * n);
    }
    return cos;
  }

int main(void){
   int y;
   scanf("%d",&y);
   printf("sine(%d)= %lf\n",y, sine(y));
   printf("cosine(%d)= %lf\n",y, cosine(y));

  return 0;
}

Приведенный выше код был реализован для вычисления синуса и косинуса с использованием ряда Тейлора. Я попытался проверить код, и он отлично работает для синуса (120). Я получаю неправильные ответы для sine (240) и sine (300).

Может кто-нибудь помочь мне выяснить, почему происходят эти ошибки?

Ответы [ 3 ]

5 голосов
/ 23 октября 2019

Вы должны вычислять функции только в первом квадранте [0, pi / 2). Используйте свойства функций, чтобы получить значения для других углов. Например, для значений x между [pi / 2, pi), sin (x) может быть вычислен как sin (pi - x).

Синус 120 градусов, то есть 40 на 90 градусов, равен 50 градусам: 40 градусов на 90. Синус начинается с 0, затем поднимается к 1 на 90 градусов, а затем снова падает взеркальное отражение к нулю при 180.

Отрицательные значения синуса от пи до 2pi просто -sin (x - pi). Я бы справился со всем этим рекурсивным определением:

sin(x):
  cases x of:
    [0, pi/2)   -> calculate (Taylor or whatever)
    [pi/2, pi)  -> sin(pi - x)
    [pi/2, 2pi) -> -sin(x - pi)
    < 0         -> sin(-x)
    >= 2pi      -> sin(fmod(x, 2pi))  // floating-point remainder

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

2 голосов
/ 23 октября 2019

Ключевой момент:

TERMS слишком мал для правильной точности. И если вы увеличите TERMS, вам придется изменить реализацию fact, так как она, вероятно, будет переполнена при работе с int.

. Я бы использовал знак для переключения мощности -1 вместо pow(-1,n)overkill.

Затем используйте double для значения PI, чтобы избежать потери слишком большого числа десятичных знаков

Затем для больших значений вам следует увеличить количество членов (это основная проблема). используя long long для вашего факториального метода или вы получите переполнение. Я установил 10 и получил правильные результаты:

#include <stdio.h>
#include <math.h>

const int TERMS = 10;
const double PI =  3.14159265358979;

long long fact(int n) {
    return n<= 0 ? 1 : n * fact(n-1);
}
double powd(double x,int n) {
    return n<= 0 ? 1 : x * powd(x,n-1);
}


double sine(int x) {
    double rad = x * (PI / 180);
    double sin = 0;


    int n;
    int sign = 1;
    for(n = 0; n < TERMS; n++) { // That's Taylor series!!
        sin += sign  * powd(rad, (2 * n) + 1)/ fact((2 * n) + 1);
        sign = -sign;
    }
    return sin;
}


double cosine(int x) {
    double rad = x * (PI / 180);
    double cos = 0;

    int n;
    int sign = 1;
    for(n = 0; n < TERMS; n++) { // That's also Taylor series!
         cos += sign * powd(rad, 2 * n) / fact(2 * n);
         sign = -sign;
    }
    return cos;
  }

int main(void){
   int y;
   scanf("%d",&y);
   printf("sine(%d)= %lf\n",y, sine(y));
   printf("cosine(%d)= %lf\n",y, cosine(y));

  return 0;
}

результат:

240
sine(240)= -0.866026
cosine(240)= -0.500001

Примечания:

  • моя рекурсивная реализация pow с использованием последовательных умноженийвероятно, не нужно, так как мы имеем дело с плавающей точкой. Он вводит ошибку накопления, если n большое.
  • fact может использовать плавающую точку, чтобы обеспечить большее число и лучшую точность. На самом деле я предложил long long, но было бы лучше не предполагать, что размера будет достаточно. Для этого лучше использовать стандартный тип, например int64_t.
  • fact и pow результаты также могут быть предварительно вычислены / жестко закодированы. Это сэкономит время вычислений.
0 голосов
/ 25 октября 2019
const double TERMS = 14;
const double PI = 3.14159265358979;

double fact(double n) {return n <= 0.0 ? 1 : n * fact(n - 1);}

double sine(double x)
{
    double rad = x * (PI / 180);
    rad = fmod(rad, 2 * PI);
    double sin = 0;

    for (double n = 0; n < TERMS; n++) 
        sin += pow(-1, n) * pow(rad, (2 * n) + 1) / fact((2 * n) + 1);

    return sin;
}

double cosine(double x)
{
    double rad = x * (PI / 180);
    rad = fmod(rad,2*PI);
    double cos = 0;

    for (double n = 0; n < TERMS; n++)
        cos += pow(-1, n) * pow(rad, 2 * n) / fact(2 * n);

    return cos;
}

 int main()
{

    printf("sine(240)= %lf\n",  sine(240));
    printf("cosine(300)= %lf\n",cosine(300));
}
...