好朋友Z说他们期末得设计一个计算器,对数ln的运算挺麻烦的。
我想,这不是就是泰勒展开的事吗?
泰勒展开1.0
立刻想到了这个嘛:
然而,跑一下程序的结果是这样的:
1.1 循环的次数多——Peano余项为o(x^n),收敛的速度慢;x越接近2,收敛就越慢。
1.2 当x>1时,增量不收敛,然后答案跑不出来了= =
因为设置的跳出循环的条件为:
while(fabs(delta/i)>Epsilon)
所以这样展开将进入死循环
面对这1.1和1.2两个缺陷,我不由得觉得自己最开始想得太天真。
泰勒展开2.0
教材上的一道例题——提高ln2的求解精度,同时解释了1.0泰勒展开的缺陷:
(《数学分析(上) 第二版》陈纪修 等著 P208 )
因为Peano余项的Taylor展开是以x逼近x0为前提的,所以当x远离0的时候,Maclaurin公式的精度将会很低
受此启发,当采用换元法之后:
这样进行Talyor展开:
2.1 因为对数函数的定义域大于0,可知——不管α取定义域内何值,t始终在0~1之间,比泰勒展开1.0更逼近x0=0
2.2 始终是收敛级数
2.3 收敛速度快——Peano余项为o(t^(2n+2)),更容易增长到更高阶
高兴地跑了跑程序:
(图中的“第一种”是泰勒展开2.0,“第二种”是泰勒展开1.0)
上述的几大特点都体现出来了——循环次数少、精度更高、适用范围更广,然而——
2.4 可知当α逼近+∞时,t逼近1,与x0=0距离太远,精度降低(图中对于10000连0.1精度都没达到)
泰勒展开3.0
目前的问题,集中在大数的求对数上了。
最开始想类比换元法,设计一个定义域控制更加严格的t的换元法,无果。
后来觉得——大数应该拆成小数!
举个例子: ln(123456)=ln(1.23456*10^5)=ln(1.23456)+5ln10
对于α小于10,用泰勒展开2.0,当α>10就视为大数,拆为k倍ln10和lnN (0
#define ln10 2.30258509299404568401
(“第一种”为泰勒展开3.0,“第二种”为泰勒展开2.0,,“第三种”为泰勒展开1.0)
泰勒展开3.0的优点是:
3.1 大数运算精确
3.2 循环次数少,收敛速度快 (0-20次,图中后两次有误,无碍)
3.3 不远离x0=0,精度高
缺点有:
3.4 对于ln0.1这样的小数运算精度反倒不如泰勒展开1.0
3.5 由于有两个lnN,不能明确确定精度范围
优化思路大抵如上,但剩下的这些缺点如何解决?实际上ln的运算是如何实现的呢?我们可以越挖越深......感谢Z提供了一个如此有思考价值的问题~
附C语言代码如下:
#include "stdafx.h"
#include
#define Epsilon 10e-6
#define ln10 2.30258509299404568401
long double ln(long double c)
{long double s2=0.0,delta;int i=1;
if(c==1) {printf("循环次数为%d\n",i-1);return 0;}
c=(c-1.0)/(c+1.0);//Marclurin公式变换技巧
delta=c;i=0;
do{ s2+=delta/(2*i+1);
delta*=c*c;i++;
}while(fabs((2*delta)/(2*i+1))>Epsilon);
printf("循环次数为%d\n",i-1);
return 2*s2;
}
int main()
{ long double c,s1=0.0,s2=0.0,input,delta,standard;int i=1,j=1,d;
while(1)
{
printf("精度为10e-6\n");
printf("ln");
scanf("%lf",&c);
if(c==1) {printf("%.10lf\n",s2);return 0;}
else { printf("第一种展开方式——————\n");//泰勒展开3.0
input=c;
for(d=0;input>=10;d++)input/=10;
printf("结果为:%.10f\n",d*ln10+ln(input));
printf("第二种展开方式——————\n");//泰勒展开2.0
printf("结果为%.10f\n",ln(c));
if(c>2.0) //泰勒展开1.0
{printf("第三种展开方式——————\n当x>1时增量不收敛,第三种展开方式进入死循环\n");
goto start;
}
input=c-1.0;//方便使用Maclaurin公式
delta=input;i=1;
do{
if(i%2) s1+=delta/i;
else s1-=delta/i;
delta*=input;i++;
}while(fabs(delta/i)>Epsilon); //当x>1时增量不收敛,进入死循环
printf("第三种展开方式——————\n");
printf("循环次数为%d\n",i);
printf("结果为%.10f\n",s1);
start: printf("Windows计算器结果为:");
scanf("%lf",&standard);
putchar('\n');
s1=s2=0.0;i=j=1;
}
}
return 0;
}