数值分析实验报告 Lab1 误差的影响

数值分析实验报告 Lab1 误差的影响

一、问题引出

(一)问题实例:

利用 n n n 阶泰勒展开多项式 ∑ i = 0 n ( x i i ! ) \sum\limits_{i=0}^n(\frac{x^i}{i!}) i=0n(i!xi) 计算函数 f ( x ) = e x f(x)=e^x f(x)=ex 在给定 x x x 的值。要求绝对误差在最大阶数 M A X N = 20 MAXN=20 MAXN=20 以内达到给定精度 E P S = 0.00001 EPS=0.00001 EPS=0.00001,即使得 ∣ ∑ i = 0 n ( x i i ! ) − e x ∣ < E P S \left|\sum\limits_{i=0}^n(\frac{x^i}{i!})-e^x\right|<EPS i=0n(i!xi)ex<EPS 并且 n < M A X N n<MAXN n<MAXN

(二)具体要求:

裁判输入数据:

1
2
3
4
5
-1
-2
-3
-4
-5
-6

标准输出数据:

2.7183
7.3891
20.0855
54.5981
-1.0000
0.3679
0.1353
0.0498
0.0183
0.0067
0.0025

注意到第5组数据中,当 x=5 时,泰勒展开多项式前20项的和产生的误差大于0.00005,达不到要求的精度0.00001,故应输出-1.0000。

(三)裁判程序如下:
#include
#include

#define EPS 0.00001
#define MAXN 20

double Exp_Calculate( double x );

int main()
{
  double x; /* 存储输入的浮点数x */

  while (scanf("%lf", &x)!= EOF)
    printf("%.4lf\n", Exp_Calculate(x));

  return 0;
}
double Exp_Calculate( double x ){
   /* 填写代码段*/
}

Tips:函数接口定义:double Exp_Calculate( double x ),其中 x 为给定点,函数返回达到精度要求的近似的值。常数 M A X N MAXN MAXN E P S EPS EPS 在裁判程序中定义。若无法在 M A X N MAXN MAXN 次叠加内达到精度,则函数返回 -1。

二、分析与实践

(一)问题分析

此处代码段重在 double Exp_Calculate( double x ) 的实现。
首先是代码模块流程的分析与建立。

  • 设定步:设定默认变量值 power=1.0(求和分子),factorial = 1.0(求和分母),sum=1.0(总和)
  • 过程步:不断迭代得到 sum 值
  • 决策步:判断 ∣ ∑ i = 0 n ( x i i ! ) − e x ∣ < E P S \left|\sum\limits_{i=0}^n(\frac{x^i}{i!})-e^x\right|<EPS i=0n(i!xi)ex<EPS
(二)实验分析

起初实现其实是个比较容易的过程。
但刚开始 程序1.0版本 是这样的:

double Exp_Calculate( double x ){

    //默认值设定
    double power=1.0;
    double factorial = 1.0;
    double sum=1.0;
    int i;
    
    //过程步求和
    for(i=1;i<MAXN;i++){
        //printf("sum[%d]=%lf\n",i,power/factorial);
        power*=x;
        factorial*=(double)i;
        sum+=power/factorial;
    }
    
    //决策步终止
    if(fabs(sum-exp(x))<EPS){return sum;}
    else{return -1;}
}

测试:部分测试正确,但是当输入-5的时候结果为-1。这就挺令人郁闷的了。
接着为了找到问题,利用如下代码块打印过程:

printf("sum[%d]=%lf\n",i,power/factorial);

结果显示:
数值分析实验报告 Lab1 误差的影响_第1张图片

其实刚开始我还没有缓过神来。经老师的指导,原来是在13行or14行以后,由于小数相近,导致相加负数或者说是相减时,小数的有效位数减少了,进而引发收敛速度变慢,最终不能得到想要的结果。而为了解决这种问题的发生,最好的方式就是防止负数的出现。

(三)解决方案

思路: ∣ ∑ i = 0 n ( x i i ! ) − e x ∣ < E P S \left|\sum\limits_{i=0}^n(\frac{x^i}{i!})-e^x\right|<EPS i=0n(i!xi)ex<EPS,由这个公式,或者说重点也就是看这个公式。

  1. 在式子中,可以发现 e x e^x ex 始终都为正数,但它存在一个等式 e x = 1 e − x e^x=\frac{1}{e^{-x}} ex=ex1,这个等式说明,当输入为负数的时候,它的计算方式是将它倒数化,进而以正数的计算方式进行。这点值得注意。
  2. 继续考虑 ∑ i = 0 n ( x i i ! ) \sum\limits_{i=0}^n(\frac{x^i}{i!}) i=0n(i!xi),可以发现,当输入的 x x x 为负数,那么 ∑ i = 0 n ( x i i ! ) \sum\limits_{i=0}^n(\frac{x^i}{i!}) i=0n(i!xi) 这个式子里的项有可能为正数,有可能为负数,进而会导致结果有损失,那么相应的解决方案就是将它正数化。
  3. 具体而言,已知 e x = 1 e − x e^x=\frac{1}{e^{-x}} ex=ex1 且当 n n n 足够大的时候, ∑ i = 0 n ( x i i ! ) ≈ e x = 1 e − x \sum\limits_{i=0}^n(\frac{x^i}{i!})\approx e^x=\frac{1}{e^{-x}} i=0n(i!xi)ex=ex1 ( 更详细的是, e x = 1 e − x ≈ ∑ i = 0 n ( x i i ! ) = 1 ∑ i = 0 n ( ( − x ) i i ! ) e^x=\frac{1}{e^{-x}}\approx \sum\limits_{i=0}^n(\frac{x^i}{i!})=\frac{1}{\sum\limits_{i=0}^n(\frac{(-x)^i}{i!})} ex=ex1i=0n(i!xi)=i=0n(i!(x)i)1 ),那么此题也可以建立在这一点上进行操作,即根据下述代码: s u m = ∑ i = 0 n ( x i i ! ) sum=\sum\limits_{i=0}^n(\frac{x^i}{i!}) sum=i=0n(i!xi),当输入的 x x x 为负数的时候,要求 ∑ i = 0 n ( x i i ! ) \sum\limits_{i=0}^n(\frac{x^i}{i!}) i=0n(i!xi) 中的项为正,结合代码部分,输入负数 x x x,将它先变为正数(而我们实际需要的是负数 x x x 对应的值),可以利用 ∑ i = 0 n ( ( − x ) i i ! ) = 1 ∑ i = 0 n ( ( x ) i i ! ) \sum\limits_{i=0}^n(\frac{(-x)^i}{i!})=\frac{1}{\sum\limits_{i=0}^n(\frac{(x)^i}{i!})} i=0n(i!(x)i)=i=0n(i!(x)i)1 式子,进而求得的 1.0 / s u m 1.0/sum 1.0/sum 就是我们所需的值。

接着可以这样加入判断进而解决问题:

    //Judge if x < 0
    if(x<0){//如果输入是负数
    
        x=-x;//先变为正数
        
        //正数运算,sum为正数
        for(i=1;i<MAXN;i++){
            //printf("sum[%d]=%lf\n",i,power/factorial);
            power*=x;
            factorial*=(double)i;
            sum+=power/factorial;
        }
        
        //-x仍然为负数,进行运算
        if(fabs(1.0/sum-exp(-x))<EPS){return 1.0/sum;}
        else{return -1;}
    }
    //if x > 0
    else{//如果输入为正数
    
        //正数运算
        for(i=1;i<MAXN;i++){
            //printf("sum[%d]=%lf\n",i,power/factorial);
            power*=x;
            factorial*=(double)i;
            sum+=power/factorial;
        }
       
       //正数运算
       if(fabs(sum-exp(x))<EPS){return sum;}
       else{return -1;}
    }

三、最终代码

以下为具体代码结果,测试均正确。

#include
#include
#define EPS 0.00001
#define MAXN 20

//函数声明段
double Exp_Calculate( double x );

//主函数段
int main()
{
  double x; /* 存储输入的浮点数x */

  while (scanf("%lf", &x)!= EOF)
    printf("%.4lf\n", Exp_Calculate(x)); /*保留四位小数*/

  return 0;
}

//函数实现段
double Exp_Calculate( double x ){

    //default value
    double power=1.0;
    double factorial = 1.0;
    double sum=1.0;
    int i;
    
    //Judge if x < 0
    if(x<0){
    
        x=-x;
        
        for(i=1;i<MAXN;i++){
            //printf("sum[%d]=%lf\n",i,power/factorial);
            power*=x;
            factorial*=(double)i;
            sum+=power/factorial;
        }
        
        if(fabs(1.0/sum-exp(-x))<EPS){return 1.0/sum;}
        else{return -1;}
    }
    //if x > 0
    else{
        for(i=1;i<MAXN;i++){
            //printf("sum[%d]=%lf\n",i,power/factorial);
            power*=x;
            factorial*=(double)i;
            sum+=power/factorial;
        }
        
       if(fabs(sum-exp(x))<EPS){return sum;}
       else{return -1;}
    }
}

*个人感想:*微乎其微的细小误差也可以造成大的灾难,在计算机的世界里,浮点数的存在也是一种艺术美。但是,我们需要更加精细,更加精确,明白数据处理关系,解决误差存在的问题。算法的优劣性不仅仅取决于效率,更需要准确来恒定。


@author Richard
@date 2018.09.19
@date 2018.10.01 modified

你可能感兴趣的:(程序员的数学,数值分析)