【问题标题】:C++ - Estimating cos(x) using Taylor Series ApproximationC++ - 使用泰勒级数近似估计 cos(x)
【发布时间】:2020-04-01 11:43:21
【问题描述】:

为了更多地练习 C++,我决定在没有数学库的帮助下做一些基本的数学函数。我做了一个幂和阶乘函数,它们似乎运行良好。但是,我的泰勒级数余弦函数有很多问题。

Wikipedia Cosine Taylor Series

它在 cos(1)、cos(2) 处输出一个很好的近似值,并在 cos(3) 和 cos(4) 处开始失去精度。除此之外,它的答案变得完全错误。以下是 ./a.out 的结果

Input an angle in radians, output will be its cosine
1
Output is: 0.540302

Input an angle in radians, output will be its cosine
2
Output is: -0.415873

Input an angle in radians, output will be its cosine
3
Output is: -0.974777

Input an angle in radians, output will be its cosine
4
Output is: -0.396825                       <-------------Should be approx. -0.654

Input an angle in radians, output will be its cosine
5
Output is: 2.5284                          <-------------Should be approx.  0.284

这里是完整的源代码:

#include <iostream>
#include <iomanip>

using std::cout;
using std::cin;
using std::endl;

int factorial(int factorial_input) {

    int original_input = factorial_input;
    int loop_length = factorial_input - 1;

    if(factorial_input == 1 || factorial_input == 0) {

        return 1;
    }

    for(int i=1; i != loop_length; i++) {

        factorial_input = factorial_input - 1;

        original_input = original_input * factorial_input;

    }

    return original_input;
}

double power(double base_input, double exponent_input) {

    double power_output = base_input;

    if(exponent_input == 0) {

        return 1;
    }

    if(base_input == 0) {

        return 0;
    }

    for(int i=0; i < exponent_input -1; i++){

        power_output = power_output * base_input;

    }
    return power_output;

}

double cos(double user_input) {

    double sequence[5] = { 0 };  //The container for each generated elemement.
    double cos_value = 0;        //The final output.
    double variable_x = 0;       //The user input x, being raised to the power 2n

    int alternating_one = 0;     //The (-1) that is being raised to the nth power,so switches back and forth from -1 to 1 
    int factorial_denom = 0;     //Factorial denominator (2n)!
    int loop_lim = sizeof(sequence)/sizeof(double);            //The upper limit of the series (where to stop), depends on size of sequence. Bigger is more precision.

    for(int n=0; n < loop_lim; n++) {

        alternating_one = power(-1, n);
        variable_x = power(user_input, (n*2));
        factorial_denom = factorial((n*2));

        sequence[n] =  alternating_one * variable_x / factorial_denom;
        cout << "Element[" << n << "] is: " << sequence[n] << endl;     //Prints out the value of each element for debugging.
    }

    //This loop sums together all the elements of the sequence.
    for(int i=0; i < loop_lim; i++) {                   

        cos_value = cos_value + sequence[i];

    }

    return cos_value;
}

int main() {

    double user_input = 0;
    double cos_output;

    cout << "Input an angle in radians, output will be its cosine" << endl;
    cin >> user_input;
    cos_output = cos(user_input);

    cout << "Output is: " << cos_output << endl;

}

根据 Desmos 上的这张图,在五次迭代中,我的函数应该保持精度直到 x > 4.2 左右:

Desmos Graph

此外,当我将系列设置为使用 20 次或更多迭代时(它会生成越来越小的数字,这应该会使答案更加精确),元素开始表现得非常不可预测。这是带有序列调试器的 ./a.out,以便我们可以看到每个元素包含的内容。输入为 1。

Input an angle in radians, output will be its cosine
1
Element[0] is: 1
Element[1] is: -0.5
Element[2] is: 0.0416667
Element[3] is: -0.00138889
Element[4] is: 2.48016e-05
Element[5] is: -2.75573e-07
Element[6] is: 2.08768e-09
Element[7] is: -7.81894e-10
Element[8] is: 4.98955e-10
Element[9] is: 1.11305e-09
Element[10] is: -4.75707e-10
Element[11] is: 1.91309e-09
Element[12] is: -1.28875e-09
Element[13] is: 5.39409e-10
Element[14] is: -7.26886e-10
Element[15] is: -7.09579e-10
Element[16] is: -4.65661e-10
Element[17] is: -inf
Element[18] is: inf
Element[19] is: -inf
Output is: -nan

谁能指出我做错了什么以及我应该做得更好吗?我是 C++ 新手,所以我仍然有很多误解。非常感谢您花时间阅读本文!

【问题讨论】:

  • 你需要利用 cos 是一个周期函数的事实。一个sin/cos周期只需要逼近1/4,其他部分可以通过镜像/重复推导出来。
  • 您的循环仅计算泰​​勒展开式的 5 项。虽然这避免了整数溢出(假设是 32 位 int) - 勉强 - 你认为这足以使计算收敛到更大的 user_input 值吗?如果您要计算泰勒展开式的五个以上项,对于较大的 user_input 值,您将如何避免在计算的任何步骤中溢出? [这些问题是提示,需要考虑以解决部分问题,顺便说一句]。

标签: c++ double precision taylor-series


【解决方案1】:

您有以下问题:


在您在图片中显示的图表中,k 包含在总和中,而您在代码中将其排除在外。因此,Desmos 图中的k=5 等于您代码中的double sequence[6] = { 0 }

这修复了user_input = 4 的输出。

对于user_input = 5,您可以与图表进行比较,看看它也给出了相似的结果(这已经与真实值相差甚远)


然后你会遇到更多项的错误,因为阶乘函数输出int,但阶乘增长如此之快以至于它会超出int可以快速保持的值范围超出范围的任何整数类型。如果您想支持更大的输入范围(虽然不多),您应该返回 double 并将 original_input 也设为 double


power 中,您将指数设为double,但将其视为整数。特别是您将它用于限制循环迭代。只有当这些值足够小,可以由double 精确表示时,它才能正常工作。一旦值变大,循环迭代的次数就会变得不精确。

使用int 作为power 的第二个参数。


如果要使用这种方法实现cos,通常会首先使用cos 对称性,以将范围缩小到更小的值,例如[0,pi/2] 首先,通过使用例如那cos(x + 2pi) = cos(x)cos(x+pi) = - cos(x)cos(-x) = cos(x)等等。

【讨论】:

  • 非常感谢!在将输入输入泰勒级数之前,我接受了您的建议并首先使用对称属性简化了输入。它现在就像一个魅力。
【解决方案2】:

问题来自您实现的factorial 函数。 我对您的代码进行了最小的更改,它对于您的 cos(1) 示例计算运行良好。只需#include &lt;cmath&gt; 并将factorial((n*2)) 替换为tgamma(2*n+1)。然后输出读取

Input an angle in radians, output will be its cosine
Element[0] is: 1
Element[1] is: -0.5
Element[2] is: 0.0416667
Element[3] is: -0.00139082
Element[4] is: 2.48022e-05
Element[5] is: -2.75573e-07
Element[6] is: 2.08768e-09
Element[7] is: 4.65661e-10
Element[8] is: -4.65661e-10
Element[9] is: 4.65661e-10
Element[10] is: -4.65661e-10
Element[11] is: 4.65661e-10
Element[12] is: -4.65661e-10
Element[13] is: 4.65661e-10
Element[14] is: -4.65661e-10
Element[15] is: 4.65661e-10
Element[16] is: -4.65661e-10
Element[17] is: 4.65661e-10
Element[18] is: -4.65661e-10
Element[19] is: 4.65661e-10
Output is: 0.5403

这是 cos(1) 的预期输出。对于 n>1 的 cos(n),问题在于 factorial_denom 的值对于整数来说变得越来越大。您应该将类​​型更改为双精度:double factorial_denom。使用您修改后的代码,我得到以下结果:

cos(1): Output is: 0.5403
cos(2): Output is: -0.416147
cos(3): Output is: -0.989992
cos(4): Output is: -0.653644
cos(5): Output is: 0.283662

Run your modified code online.

【讨论】:

  • 我不知道 的 gamma 函数。尽管我打算不将这个程序库用作一个挑战,但这绝对是一种更实用的方法。谢谢,BlueTune!
  • @Sidney Janssen:感谢您的反馈!我不知道你想坚持你的自定义阶乘。如果您将阶乘函数更改为 double factorial(double factorial_input)(这会导致一些其他小的更改),您的代码可以使用您的自定义阶乘函数运行良好:Run the code online
【解决方案3】:

除了已经建议的更改之外,请考虑将系列的使用限制在相对较窄的输入范围内。对于非常大的角度,您可能会遇到数值问题,它们会增加您需要进行的测试量。

余弦函数有多个恒等式,例如对于任意整数n,cos(x) = cos(-x) 和cos(x) = cos(n*2*pi+x)。在运行您的系列解决方案之前,使用这些将角度减小到有限范围。

【讨论】:

    猜你喜欢
    • 2020-03-01
    • 2021-08-12
    • 2015-04-30
    • 1970-01-01
    • 2015-11-21
    • 2016-08-09
    • 1970-01-01
    • 1970-01-01
    • 2022-06-10
    相关资源
    最近更新 更多