负 x 的泰勒级数 e^x 的误差
Error of Taylor Series e^x for negative x
我在使用泰勒级数计算 e^x 时注意到当我们计算负 x 时绝对误差是 large.Is 是因为我们没有足够的精度来计算它?
(我知道为了防止它我们可以使用 e^(-x)=1/e^x)
#include <stdio.h>
#include <math.h>
double Exp(double x);
int main(void)
{
double x;
printf("x=");
scanf("%le", &x);
printf("%le", Exp(x));
return 0;
}
double Exp(double x)
{
double h, eps = 1.e-16, Sum = 1.0;
int i = 2;
h = x;
do
{
Sum += h;
h *= x / i;
i++;
} while (fabs(h) > eps);
return Sum ;
}
例如:
x=-40 值是 4.24835e-18 但程序给了我 3.116952e-01.The 绝对误差是 ~0.311
x=-50 值是 1.92875e-22 programm 给我 2.041833e+03.The 绝对误差是 ~2041.833
问题是由算法中间阶段的舍入误差引起的。
h
与 40/2 * 40/3 * 40 / 4 * ...
一样快速增长,并且在符号上振荡。 i
、h
和 Sum
的 x=-40
连续迭代的值可以在下面找到(为简洁起见省略了一些数据点):
x=-40
i=2 h=800 Sum=-39
i=3 h=-10666.7 Sum=761
i=4 h=106667 Sum=-9905.67
i=5 h=-853333 Sum=96761
i=6 h=5.68889e+06 Sum=-756572
...
i=37 h=-1.37241e+16 Sum=6.63949e+15
i=38 h=1.44464e+16 Sum=-7.08457e+15
i=39 h=-1.48168e+16 Sum=7.36181e+15
i=40 h=1.48168e+16 Sum=-7.45499e+15
i=41 h=-1.44554e+16 Sum=7.36181e+15
i=42 h=1.37671e+16 Sum=-7.09361e+15
i=43 h=-1.28066e+16 Sum=6.67346e+15
i=44 h=1.16423e+16 Sum=-6.13311e+15
i=45 h=-1.03487e+16 Sum=5.50923e+15
i=46 h=8.99891e+15 Sum=-4.83952e+15
...
i=97 h=-2610.22 Sum=1852.36
i=98 h=1065.4 Sum=-757.861
i=99 h=-430.463 Sum=307.534
...
i=138 h=1.75514e-16 Sum=0.311695
i=139 h=-5.05076e-17 Sum=0.311695
3.116952e-01
总和的峰值幅度为7e15
。这就是精度丢失的地方。类型 double
的表示精度约为 1e-16
。这给出了大约 0.1 - 1
的预期绝对误差。
由于预期和(exp(-40)
的值接近于零,最终绝对误差接近于部分和的最大绝对误差。
对于 x=-50
和的峰值是 1.5e20
由于 double
的有限表示而给出的绝对误差大约 1e3 - 1e4
接近观察到的.
如果不对算法进行重大更改以避免形成这些部分和,则无法解决太多问题。或者,将 exp(-x)
计算为 1/exp(x)
.
对于负 x,即使在 1.0 + x
的第一个总和中,添加交替的 +/- 项也会产生计算问题,因为最终的总和误差预计与 1.0 的最低有效位一样糟糕或大约 1016 中的 1 份。这意味着 x_min
因为 Exp(x_min) == 1.0e-16
是最小有用的计算值(例如 x
大约 -36)
一个简单的解决方案是形成一个好的 Exp(positive_x)
和负值 ...
double Exp(double x) {
if (x < 0) {
return 1.0 / Exp(-x);
}
...
一个好的(和简单的)Exp(positive_x)
计算项直到 term + 1.0
仍然是 1.0,因为额外的小项不会显着改变总和。适用于 all x
(非常小的错误)除了当结果应该是次正常时可以使用改进。
double my_exp(double x) {
if (x < 0) {
return 1.0 / my_exp(-x);
}
double sum = 1.0;
unsigned n = 1;
double term = 1.0;
do {
term *= x / n++;
sum += term;
if (!isfinite(term)) {
return term;
}
} while (1.0 != term + 1.0);
return sum;
}
我在使用泰勒级数计算 e^x 时注意到当我们计算负 x 时绝对误差是 large.Is 是因为我们没有足够的精度来计算它?
(我知道为了防止它我们可以使用 e^(-x)=1/e^x)
#include <stdio.h>
#include <math.h>
double Exp(double x);
int main(void)
{
double x;
printf("x=");
scanf("%le", &x);
printf("%le", Exp(x));
return 0;
}
double Exp(double x)
{
double h, eps = 1.e-16, Sum = 1.0;
int i = 2;
h = x;
do
{
Sum += h;
h *= x / i;
i++;
} while (fabs(h) > eps);
return Sum ;
}
例如: x=-40 值是 4.24835e-18 但程序给了我 3.116952e-01.The 绝对误差是 ~0.311
x=-50 值是 1.92875e-22 programm 给我 2.041833e+03.The 绝对误差是 ~2041.833
问题是由算法中间阶段的舍入误差引起的。
h
与 40/2 * 40/3 * 40 / 4 * ...
一样快速增长,并且在符号上振荡。 i
、h
和 Sum
的 x=-40
连续迭代的值可以在下面找到(为简洁起见省略了一些数据点):
x=-40
i=2 h=800 Sum=-39
i=3 h=-10666.7 Sum=761
i=4 h=106667 Sum=-9905.67
i=5 h=-853333 Sum=96761
i=6 h=5.68889e+06 Sum=-756572
...
i=37 h=-1.37241e+16 Sum=6.63949e+15
i=38 h=1.44464e+16 Sum=-7.08457e+15
i=39 h=-1.48168e+16 Sum=7.36181e+15
i=40 h=1.48168e+16 Sum=-7.45499e+15
i=41 h=-1.44554e+16 Sum=7.36181e+15
i=42 h=1.37671e+16 Sum=-7.09361e+15
i=43 h=-1.28066e+16 Sum=6.67346e+15
i=44 h=1.16423e+16 Sum=-6.13311e+15
i=45 h=-1.03487e+16 Sum=5.50923e+15
i=46 h=8.99891e+15 Sum=-4.83952e+15
...
i=97 h=-2610.22 Sum=1852.36
i=98 h=1065.4 Sum=-757.861
i=99 h=-430.463 Sum=307.534
...
i=138 h=1.75514e-16 Sum=0.311695
i=139 h=-5.05076e-17 Sum=0.311695
3.116952e-01
总和的峰值幅度为7e15
。这就是精度丢失的地方。类型 double
的表示精度约为 1e-16
。这给出了大约 0.1 - 1
的预期绝对误差。
由于预期和(exp(-40)
的值接近于零,最终绝对误差接近于部分和的最大绝对误差。
对于 x=-50
和的峰值是 1.5e20
由于 double
的有限表示而给出的绝对误差大约 1e3 - 1e4
接近观察到的.
如果不对算法进行重大更改以避免形成这些部分和,则无法解决太多问题。或者,将 exp(-x)
计算为 1/exp(x)
.
对于负 x,即使在 1.0 + x
的第一个总和中,添加交替的 +/- 项也会产生计算问题,因为最终的总和误差预计与 1.0 的最低有效位一样糟糕或大约 1016 中的 1 份。这意味着 x_min
因为 Exp(x_min) == 1.0e-16
是最小有用的计算值(例如 x
大约 -36)
一个简单的解决方案是形成一个好的 Exp(positive_x)
和负值 ...
double Exp(double x) {
if (x < 0) {
return 1.0 / Exp(-x);
}
...
一个好的(和简单的)Exp(positive_x)
计算项直到 term + 1.0
仍然是 1.0,因为额外的小项不会显着改变总和。适用于 all x
(非常小的错误)除了当结果应该是次正常时可以使用改进。
double my_exp(double x) {
if (x < 0) {
return 1.0 / my_exp(-x);
}
double sum = 1.0;
unsigned n = 1;
double term = 1.0;
do {
term *= x / n++;
sum += term;
if (!isfinite(term)) {
return term;
}
} while (1.0 != term + 1.0);
return sum;
}