【问题标题】:Taylor Series in C (problem with sin(240) and sin(300))C 中的泰勒级数(sin(240) 和 sin(300) 的问题)
【发布时间】:2020-02-20 00:58:34
【问题描述】:
#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)。 我得到了正弦(240)和正弦(300)的错误答案。

谁能帮我找出这些错误发生的原因?

【问题讨论】:

标签: c taylor-series


【解决方案1】:

您应该只计算第一象限中的函数 [0, pi/2)。利用函数的属性来获取其他角度的值。例如,对于 [pi/2, pi) 之间的 x 值,sin(x) 可以通过 sin(pi - x) 计算。

120 度的正弦,即 40 到 90 度,与 50 度相同:90 之前的 40 度。正弦从 0 开始,然后在 90 度处向 1 上升,然后再次镜像下降到180 处为零。

从 pi 到 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 的类似方法,使用适合它的身份案例。

【讨论】:

  • OP 代码中使用的幼稚服务收敛速度很慢,这也是一个重要的问题。选择一个收敛速度更快的系列会有所帮助。
  • @dmckee,这就是在这个答案中提出建议的原因。如果将角度的绝对值限制在一个弧度以下,则该系列的收敛速度非常快,随着您离开原点值变得越来越慢(在0.0 所示的情况下)
【解决方案2】:

重点是:

TERMS 太小而无法获得适当的精度。如果增加TERMS,则必须更改fact 的实现,因为在使用int 时它可能会溢出。

我会使用符号来切换 -1 电源,而不是 pow(-1,n) 过度杀伤。

然后用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 这样的标准类型。
  • factpow 结果也可以预先计算/硬编码。这将节省计算时间。

【讨论】:

  • 另外,请注意关于更高浮点精度的问题和答案中提出的一些要点:stackoverflow.com/questions/13516476/…
  • 我认为您的更改过多(尽管可能会有所改进)。 OP 代码的核心问题是fact 的溢出,也可能是TERMS 的不足。
  • 是的,你是对的。我在答案末尾的注释解释了这一点。关键点是:如果 TERMS 很小,那么会损失精度,如果 TERMS >= 7,除非使用 int64_t 或 double,否则会溢出。如果您认为您可以编辑答案以使其更专注于真正的问题,我认为这没有问题。
  • 系数也可以全部预先计算,而不仅仅是事实。
  • 真的!编写答案时出现改进想法的示例
【解决方案3】:
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));
}

【讨论】:

    猜你喜欢
    • 2018-06-23
    • 2021-08-13
    • 1970-01-01
    • 2021-03-19
    • 2016-06-04
    • 2022-06-10
    • 1970-01-01
    • 2016-02-01
    相关资源
    最近更新 更多