C++ - 使用泰勒级数近似估计 cos(x)
C++ - Estimating cos(x) using Taylor Series Approximation
为了在 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++ 的新手,所以我仍然有很多误解。非常感谢您花时间阅读本文!
您遇到以下问题:
在图片中显示的图表中 k
包含在总和中,而您在代码中将其排除。因此,Desmos 图中的 k=5
等于代码中的 double sequence[6] = { 0 }
。
这修复了 user_input = 4
的输出。
对于 user_input = 5
,您可以与图表进行比较,看看它也给出了相似的结果(已经远离真实值)
然后你会遇到更多项的错误,因为阶乘函数输出 int
,但阶乘增长得太快以至于它会超出值的范围 int
可以快速保存,也可以快速超出任何整数类型的范围。你应该 return 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)
,等等
问题出在您实现的 factorial
功能上。
我对您的代码进行了极小的改动,它在您的 cos(1) 示例计算中运行良好。只需 #include <cmath>
并将 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
除了已经建议的更改之外,请考虑将系列的使用限制在相对较窄的输入范围内。对于非常大的角度,您可能会遇到数值问题,它们会增加您需要进行的测试量。
余弦函数有几个恒等式,例如对于任意整数n,cos(x) = cos(-x) 和cos(x) = cos(n*2*pi+x)。在 运行 您的系列解决方案之前,使用这些将角度减小到有限范围。
为了在 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++ 的新手,所以我仍然有很多误解。非常感谢您花时间阅读本文!
您遇到以下问题:
在图片中显示的图表中 k
包含在总和中,而您在代码中将其排除。因此,Desmos 图中的 k=5
等于代码中的 double sequence[6] = { 0 }
。
这修复了 user_input = 4
的输出。
对于 user_input = 5
,您可以与图表进行比较,看看它也给出了相似的结果(已经远离真实值)
然后你会遇到更多项的错误,因为阶乘函数输出 int
,但阶乘增长得太快以至于它会超出值的范围 int
可以快速保存,也可以快速超出任何整数类型的范围。你应该 return 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)
,等等
问题出在您实现的 factorial
功能上。
我对您的代码进行了极小的改动,它在您的 cos(1) 示例计算中运行良好。只需 #include <cmath>
并将 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
除了已经建议的更改之外,请考虑将系列的使用限制在相对较窄的输入范围内。对于非常大的角度,您可能会遇到数值问题,它们会增加您需要进行的测试量。
余弦函数有几个恒等式,例如对于任意整数n,cos(x) = cos(-x) 和cos(x) = cos(n*2*pi+x)。在 运行 您的系列解决方案之前,使用这些将角度减小到有限范围。