Taylor系列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 =
for(int i = 1; i <200; i ++){
last * = x / i; //将前一项中的最后一个项乘以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条款,你应该准确的例如。[ - 0.5,0.5]
范围内10 ^( - 60)
但x = 100
泰勒定理只会给你一个非常可怕的约束。
从概念上讲, c $ c> e ^ { - x} 趋于零,因为
x
变为负无穷大。但是你的近似函数是一个固定程度的多项式,任何非常数多项式倾向于无穷大或无限渐近加无穷大。因此,如果您考虑x
的所有可能值的范围,相对误差必须是无限的。
我想你应该重新思考你的做法。你可以考虑的一个事情是,只使用泰勒级数法满足
x
的值满足-0.5f <= x <= 0.5f
。对于
x
大于0.5f
,您尝试将x
两个并递归调用该函数,然后对结果求平方。
为了获得最佳效果,您应该使用已建立的方法。
p>
我决定写一些代码,看看我的想法是如何工作。它似乎完全符合范围
x = -10000
到x = 10000
的C库实现至少到与我显示的精度一样多。 :)
请注意,即使对于
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; //将前一项中的最后一个项乘以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 =<混合(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
库= 0.367879
X = -0.1
泰勒级数= 0.904837
混合方法= 0.904837
库= 0.904837
X = 0
泰勒级数= 1
hybrid method = 1
library = 1
x = 0.1
taylor series = 1.10517
hybrid method = 1.10517
library = 1.10517
x = 1
泰勒级数= 2.71828
混合方法= 2.71828
库= 2.71828
X = 10
泰勒级数= 22026.5
混合方法= 22026.5
库= 22026.5
x = 100
taylor series = 2.68812e + 43
混合法= 2.68812e + 43
库= 2.68812e + 43
x = 1000
泰勒系列= 3.16501e + 224
混合方法= 1.97007e + 434
库= 1.97007e + 434
X = 10000
泰勒级数= 2.58744e + 423
混合方法= 8.80682 e + 4342
library = 8.80682e + 4342
编辑:
有关谁是有兴趣:
有在对浮点错误是如何显著是在原计划的意见中提出的一些问题。我原来的假设是他们是微不足道的 - 我做了一个测试,看看是否是真的。事实证明,这不是真的,且有显著浮点错误怎么回事,但即使没有浮点错误,也有仅通过泰勒级数引入显著的错误。泰勒系列在
x = -100
时的真实价值似乎接近-10 ^ {24}
,而不是10 ^ { - 44}
。我检查了这一点使用的boost ::多倍:: cpp_rational
,这是建立在他们的任意精度的整数类型的任意精度理智型。
输出:
x = -100
taylor series(double)= -8.49406e + 24
(理性)= -18893676108550916857809762858135399218622904499152741157985438973568808515840901824148153378967545615159911801257288730703818783811465589393308637433853828075746484162303774416145637877964256819225743503057927703756503421797985867950089388433370741907279634245166982027749118060939789786116368342096247737/2232616279628214542925453719111453368125414939204152540389632950466163724817295723266374721466940218188641069650613086131881282494641669993119717482562506576264729344137595063634080983904636687834775755173984034571100264999493261311453647876869630211032375288916556801211263293563
= -8.46257e + 24
库= 3.72008e-44
X = 100
泰勒(双)= 2.68812e +43
(理性)= 36451035284924577938246208798747009164319474757880246359883694555113407009453436064573518999387789077985197279221655719227002367495061633272603038249747260895707250896595889294145309676586627989388740458641362406969609459453916777341749316070359589697827702813520519796940239276744754778199440304584107317957027129587503199/1356006206645357299077422810994072904566969809700681604285727988319939931024001696953196916719184549697395496290863162742676361760549235149195411231740418104602504325580502523311497039304043141691060121240640609954226541318710631103275528465092597490136227936213123455950399178299
= 2.68812e + 43
库= 2.68812e + 43
代码:
#include< cmath>
#include< iostream>
#include< boost / multiprecision / cpp_int.hpp>
typedef unsigned int uint;
typedef boost :: multiprecision :: cpp_rational rational;
// Taylor系列exp
模板< typename T>
T tlorlor_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);
}
I'm trying to write a program which calculates the Taylor series of exp(-x) and exp(x) up to 200 iterations, for large x. (exp(x)=1+x+x^2/2+...).
My program is really simple, and it seems like it should work perfectly. However it diverges for exp(-x), but converges just fine for exp(+x). Here is my code so far:
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;
And when I run it, I get the following output:
exp(+x) = 2.68811691354e+43 exp(-x) = -8.42078025179e+24
When the actual values are:
exp(+x) = 2.68811714182e+43 exp(-x) = 3.72007597602e-44
As you can see, it works just fine for the positive calculation, but not negative. Does anyone have any ideas on why rounding errors could go so wrong by just adding a negative on every other term? Also, is there anything I could implement to fix this issue?
Thanks in advance!!
解决方案I don't think this actually has anything to do with floating point approximation error, I think there is another more significant source of error.
As you say yourself, your method is really simple. You are taking a taylor series approximation at
x=0
to this function, and then evaluating it atx=-100
.How accurate did you actually expect this method to be and why?
At a high level, you should only expect your method to be accurate in a narrow region near
x=0
. Taylor's approximation theorem tells you that e.g. if you takeN
terms of the series aroundx=0
, your approximation will be accurate toO(|x|)^(N+1)
at least. So if you take 200 terms, you should be accurate to e.g. within10^(-60)
or so in the range[-0.5, 0.5]
. But atx=100
Taylor's theorem only gives you a pretty terrible bound.Conceptually, you know that
e^{-x}
tends to zero asx
goes to minus infinity. But your approximation function is a polynomial of a fixed degree, and any non-constant polynomial tends to either plus infinity or minus infinity asymptotically. So the relative error must be unbounded if you consider the whole range of possible values ofx
.So in short I think you should rethink your approach. One thing you might consider is, only use the Taylor series method for
x
values satisfying-0.5f <= x <= 0.5f
. And for anyx
greater than0.5f
, you try dividingx
by two and calling the function recursively, then squaring the result. Or something like this.For best results you should probably use an established method.
Edit:
I decided to write some code to see how well my idea actually works. It appears to match perfectly with the C library implementation across the range
x = -10000
tox = 10000
at least to as many bits of precision as I am displaying. :)Note also that my method is accurate even for values of
x
greater than 100, where the Taylor Series method actually loses accuracy on the positive end as well.#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); }
Output:
$ ./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
Edit:
For who is interested:
There was some question raised in the comments about how significant the floating point errors are in the original program. My original assumption was that they were negligible -- I did a test to see if that's true or not. It turns out that's not true, and there is significant floating point error going on, but that even without the floating point errors there are significant errors being introduced just by the taylor series alone. The true value of the taylor series at 200 terms at
x=-100
seems to be near to-10^{24}
, not10^{-44}
. I checked this usingboost::multiprecision::cpp_rational
, which is an arbitrary precision rational type built on their arbitrary precision integer type.Output:
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
Code:
#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); }
这篇关于Taylor系列exp(-x)和exp(+ x)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!