泰勒级数exp(-x)和exp(+x)的差
Taylor Series Difference between exp(-x) and exp(+x)
我正在尝试写一个程序,计算exp(-x)和exp(x)的泰勒级数,最多200次迭代,对于大x (exp(x)=1+x+x^2/2+…)。
我的程序非常简单,看起来应该可以完美地工作。但是对于exp(-x)是发散的,对于exp(+x)是收敛的。下面是我到目前为止的代码:
long double x = 100.0, sum = 1.0, last = 1.0;
for(int i = 1; i < 200; i++) {
last *= x / i; //multiply the last term in the series from the previous term by x/n
sum += last; //add this new last term to the sum
}
cout << "exp(+x) = " << sum << endl;
x = -100.0; //redo but now letting x<0
sum = 1.0;
last = 1.0;
for(int i = 1; i < 200; i++) {
last *= x / i;
sum += last;
}
cout << "exp(-x) = " << sum << endl;
当我运行它时,我得到以下输出:
exp(+x) = 2.68811691354e+43
exp(-x) = -8.42078025179e+24
当实际值为:
exp(+x) = 2.68811714182e+43
exp(-x) = 3.72007597602e-44
可以看到,它可以很好地用于正计算,但不能用于负计算。有人知道为什么四舍五入误差会如此之大,仅仅在每一项上加一个负数吗?此外,有什么我可以实现来解决这个问题吗?
提前感谢!!
我不认为这实际上与浮点近似误差有任何关系,我认为还有另一个更重要的误差来源。
正如你自己所说,你的方法真的很简单。你在x=0
处对这个函数取泰勒级数近似,然后在x=-100
处求值。
你实际期望这个方法有多精确,为什么?
在较高的水平上,你应该只期望你的方法在x=0
附近的狭窄区域是准确的。泰勒近似定理告诉你,例如,如果你在x=0
周围的级数中取N
项,你的近似至少会精确到O(|x|)^(N+1)
。因此,如果你取200项,你应该准确到例如在10^(-60)
或[-0.5, 0.5]
范围内。但在x=100
处,泰勒定理只给出了一个非常糟糕的边界。
从概念上讲,当x
趋于负无穷时,你知道e^{-x}
趋于零。但是你的近似函数是一个固定次的多项式,任何非常数多项式都趋向于正无穷或负无穷渐近。因此,如果考虑x
的整个可能值范围,相对误差必须是无界的。
所以简而言之,我认为你应该重新考虑你的方法。您可能会考虑的一件事是,仅对满足-0.5f <= x <= 0.5f
的x
值使用泰勒级数方法。对于任何大于0.5f
的x
,尝试将x
除以2并递归调用该函数,然后对结果平方。或者类似这样的。
为了获得最佳效果,您可能应该使用已建立的方法。
编辑:我决定写一些代码来看看我的想法实际上是如何工作的。它似乎与C库实现在x = -10000
到x = 10000
的范围内完美匹配,至少与我显示的精度一样多。:)
还请注意,即使x
大于100,我的方法也是准确的,其中泰勒级数方法实际上也会在正端失去精度。
#include <cmath>
#include <iostream>
long double taylor_series(long double x)
{
long double sum = 1.0, last = 1.0;
for(int i = 1; i < 200; i++) {
last *= x / i; //multiply the last term in the series from the previous term by x/n
sum += last; //add this new last term to the sum
}
return sum;
}
long double hybrid(long double x)
{
long double temp;
if (-0.5 <= x && x <= 0.5) {
return taylor_series(x);
} else {
temp = hybrid(x / 2);
return (temp * temp);
}
}
long double true_value(long double x) {
return expl(x);
}
void output_samples(long double x) {
std::cout << "x = " << x << std::endl;
std::cout << "ttaylor series = " << taylor_series(x) << std::endl;
std::cout << "thybrid method = " << hybrid(x) << std::endl;
std::cout << "tlibrary = " << true_value(x) << std::endl;
}
int main() {
output_samples(-10000);
output_samples(-1000);
output_samples(-100);
output_samples(-10);
output_samples(-1);
output_samples(-0.1);
output_samples(0);
output_samples(0.1);
output_samples(1);
output_samples(10);
output_samples(100);
output_samples(1000);
output_samples(10000);
}
输出:$ ./main
x = -10000
taylor series = -2.48647e+423
hybrid method = 1.13548e-4343
library = 1.13548e-4343
x = -1000
taylor series = -2.11476e+224
hybrid method = 5.07596e-435
library = 5.07596e-435
x = -100
taylor series = -8.49406e+24
hybrid method = 3.72008e-44
library = 3.72008e-44
x = -10
taylor series = 4.53999e-05
hybrid method = 4.53999e-05
library = 4.53999e-05
x = -1
taylor series = 0.367879
hybrid method = 0.367879
library = 0.367879
x = -0.1
taylor series = 0.904837
hybrid method = 0.904837
library = 0.904837
x = 0
taylor series = 1
hybrid method = 1
library = 1
x = 0.1
taylor series = 1.10517
hybrid method = 1.10517
library = 1.10517
x = 1
taylor series = 2.71828
hybrid method = 2.71828
library = 2.71828
x = 10
taylor series = 22026.5
hybrid method = 22026.5
library = 22026.5
x = 100
taylor series = 2.68812e+43
hybrid method = 2.68812e+43
library = 2.68812e+43
x = 1000
taylor series = 3.16501e+224
hybrid method = 1.97007e+434
library = 1.97007e+434
x = 10000
taylor series = 2.58744e+423
hybrid method = 8.80682e+4342
library = 8.80682e+4342
编辑:感兴趣者:
在注释中提出了一些问题,关于原始程序中的浮点错误有多重要。我最初的假设是它们可以忽略不计——我做了一个测试来看看这是否正确。但事实并非如此,这里有明显的浮点误差,但即使没有浮点误差也会有明显的误差仅仅是由泰勒级数引入的。200项泰勒级数在x=-100
处的真实值似乎接近-10^{24}
,而不是10^{-44}
。我使用boost::multiprecision::cpp_rational
检查了这一点,这是一个建立在任意精度整数类型上的任意精度有理类型。
x = -100
taylor series (double) = -8.49406e+24
(rational) = -18893676108550916857809762858135399218622904499152741157985438973568808515840901824148153378967545615159911801257288730703818783811465589393308637433853828075746484162303774416145637877964256819225743503057927703756503421797985867950089388433370741907279634245166982027749118060939789786116368342096247737/2232616279628214542925453719111453368125414939204152540389632950466163724817295723266374721466940218188641069650613086131881282494641669993119717482562506576264729344137595063634080983904636687834775755173984034571100264999493261311453647876869630211032375288916556801211263293563
= -8.46257e+24
library = 3.72008e-44
x = 100
taylor series (double) = 2.68812e+43
(rational) = 36451035284924577938246208798747009164319474757880246359883694555113407009453436064573518999387789077985197279221655719227002367495061633272603038249747260895707250896595889294145309676586627989388740458641362406969609459453916777341749316070359589697827702813520519796940239276744754778199440304584107317957027129587503199/1356006206645357299077422810994072904566969809700681604285727988319939931024001696953196916719184549697395496290863162742676361760549235149195411231740418104602504325580502523311497039304043141691060121240640609954226541318710631103275528465092597490136227936213123455950399178299
= 2.68812e+43
library = 2.68812e+43
代码:#include <cmath>
#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>
typedef unsigned int uint;
typedef boost::multiprecision::cpp_rational rational;
// Taylor series of exp
template <typename T>
T taylor_series(const T x) {
T sum = 1, last = 1;
for (uint i = 1; i < 200; i++) {
last = last * (x / i);
sum = sum + last;
}
return sum;
}
void sample(const int x) {
std::cout << "x = " << x << std::endl;
long double e1 = taylor_series(static_cast<long double>(x));
std::cout << "ttaylor series (double) = " << e1 << std::endl;
rational e2 = taylor_series(static_cast<rational>(x));
std::cout << "t (rational) = " << e2 << std::endl;
std::cout << "t = " << static_cast<long double>(e2) << std::endl;
std::cout << "tlibrary = " << expl(static_cast<long double>(x)) << std::endl;
}
int main() {
sample(-100);
sample(100);
}
使用泰勒多项式可能不是一个好主意;关于使用切比雪夫多项式进行函数逼近的好文章,请参阅http://www.embeddedrelated.com/showarticle/152.php。
rickandross指出了这种情况下错误的来源,即exp(-100)的泰勒展开涉及大值的差异。
对Taylor尝试有一个简单的修改,可以为我尝试的几个测试用例获得合理的答案,即使用exp(-x) = 1/exp(x)这一事实。这个项目:
#include <iostream>
#include <cmath>
double texp(double x)
{
double last=1.0;
double sum=1.0;
if(x<0)
return 1/texp(-x);
for(int i = 1; i < 200; i++) {
last *= x / i;
sum += last;
}
return sum;
}
void test_texp(double x)
{
double te=texp(x);
double e=std::exp(x);
double err=te-e;
double rerr=(te-e)/e;
std::cout << "x=" << x
<< "ttexp(x)=" << te
<< "texp(x)=" << e
<< "terr=" << err
<< "trerr=" << rerr
<< "n";
}
int main()
{
test_texp(0);
test_texp(1);
test_texp(-1);
test_texp(100);
test_texp(-100);
}
给出如下输出(注意双精度在2e-16左右):
x=0 texp(x)=1 exp(x)=1 err=0 rerr=0
x=1 texp(x)=2.71828 exp(x)=2.71828 err=4.44089e-16 rerr=1.63371e-16
x=-1 texp(x)=0.367879 exp(x)=0.367879 err=-5.55112e-17 rerr=-1.50895e-16
x=100 texp(x)=2.68812e+43 exp(x)=2.68812e+43 err=1.48553e+28 rerr=5.52628e-16
x=-100 texp(x)=3.72008e-44 exp(x)=3.72008e-44 err=-2.48921e-59 rerr=-6.69128e-16
- 将向量之间的数字放在另一个向量之间<vector>>如果两个数字的差值为 1
- 我必须计算方阵的对角线元素之和的差值
- 为什么 2 个双精度值之间的差值计算错误?
- 赋值运算符之间的差(+=和=)
- 计算C++两倍的差值
- 如果 2 个数字之间的差值在 1 到 100 之间,则应运行 if 语句
- 如何使用特征计算C++中稀疏矩阵的差分
- 为什么我会得到std::exp的平台特定结果
- 出乎意料的差执行时间,用于字符串串联函数
- 如何计算C 中两个数字的差
- C++计算两个off64_t数字之间的差值
- 如何创建一个32位int和四个8位char类型的并集,每个类型都引用32位int的差分片
- 交换两个序列的元素,使元素和的差最小
- 设计一个结构来存储时间和日期.写一个函数来计算两个时间之间的差(以分钟为单位)
- 泰勒级数exp(-x)和exp(+x)的差
- EXP的并行化
- 求两个温度之间的差
- 试着计算两次之间的差,不要相减
- 0 和 128 之间的差值
- 没有自然排序顺序的两个std::向量之间的差