数值分析实验报告 Lab1 误差的影响
一、问题引出
(一)问题实例:
利用 阶泰勒展开多项式 计算函数 在给定 的值。要求绝对误差在最大阶数 以内达到给定精度 ,即使得 并且 。
(二)具体要求:
裁判输入数据:
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<stdio.h>
#include<math.h>
#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
为给定点,函数返回达到精度要求的近似的值。常数 和 在裁判程序中定义。若无法在 次叠加内达到精度,则函数返回 -1。
二、分析与实践
(一)问题分析
此处代码段重在 double Exp_Calculate( double x )
的实现。
首先是代码模块流程的分析与建立。
- 设定步:设定默认变量值 power=1.0(求和分子),factorial = 1.0(求和分母),sum=1.0(总和)
- 过程步:不断迭代得到 sum 值
- 决策步:判断
(二)实验分析
起初实现其实是个比较容易的过程。
但刚开始 程序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);
结果显示:
其实刚开始我还没有缓过神来。经老师的指导,原来是在13行or14行以后,由于小数相近,导致相加负数或者说是相减时,小数的有效位数减少了,进而引发收敛速度变慢,最终不能得到想要的结果。而为了解决这种问题的发生,最好的方式就是防止负数的出现。
(三)解决方案
思路: ,由这个公式,或者说重点也就是看这个公式。
- 在式子中,可以发现 始终都为正数,但它存在一个等式 ,这个等式说明,当输入为负数的时候,它的计算方式是将它倒数化,进而以正数的计算方式进行。这点值得注意。
- 继续考虑 ,可以发现,当输入的 为负数,那么 这个式子里的项有可能为正数,有可能为负数,进而会导致结果有损失,那么相应的解决方案就是将它正数化。
- 具体而言,已知 且当 足够大的时候, ( 更详细的是, ),那么此题也可以建立在这一点上进行操作,即根据下述代码: ,当输入的 为负数的时候,要求 中的项为正,结合代码部分,输入负数 ,将它先变为正数(而我们实际需要的是负数 对应的值),可以利用 式子,进而求得的 就是我们所需的值。
接着可以这样加入判断进而解决问题:
//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<stdio.h>
#include<math.h>
#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