正文

简单方法算pi和e,计算到小数点后面一万位2007-03-29 15:20:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/rickone/24388.html

分享到:

简单方法算pie,计算到小数点后面一万位

 

1,  计算pi(圆周率)

pi的公式非常之多,下面是几个有趣的:

pi/4=1+(1*1/(2+(3*3/(2+(5*5/(2+…))))))  [布朗克连分式]

2/pi=(sqrt(2)/2)*(sqrt(2+sqrt(2))/2)*(sqrt(2+sqrt(2+sqrt(2)))/2)*… [韦达恒等式]

pi/4=(2/1)*(2/3)*(4/3)*(4/5)*(6/5)*(6/7)*(8/7)*(8/9)*… [华里达表达式]

pi*pi/6=1/1*1+1/2*2+1/3*3+… [欧拉等式]

 

虽然这些连分式、无穷级数、无穷乘积都收敛到pi或和pi相关的数值,但是收敛速度却各不相同,收敛和收敛速度是两个完全不同的概念。

 

现在PC机上很有名的SuperPi,采用了Gauss-Legendre算法,这个公式每迭代一次将得到双倍的十进制精度,比如要计算100万位,迭代20次就够了。虽然收敛速度是超线性的,但是随着展开的数据位数越来越高,迭代一次的代价会越来越大,这关系到高精度算法,实现起来相当复杂。高精度,这是个值得思考的问题。

 

在这里采用一个简单的公式计算,Machin公式:pi=16arctg(1/5)-4arctg(1/239) (可以把这个表达式输入到google上,然后google会告诉你结果是3.1415926

 

下面从arctg(x)的展开出发,简单讨论一下这个公式。

 

可以从等比数列的极限开始:设|x|<11+x+x^2+x^3+…=1/(1-x),等式左边是无限项,如果只取有限的n项,结果会是什么呢?这关系到一个余项的的问题,对右边的式子进行泰勒展开,有泰勒余项,于是:1/(1-x)=1+x+x^2+…+x^n+  x^(n+1)/(1-eps)^(n+1)  1),eps0x之间,最后的那个余项是进行误差分析的基础,也是决定收敛速度的式子。(1)只展开到有限项,于是对它两边进行代换得到,

1/(1+x^2)=1-x^2+x^4-…+(-1)^n*x^(2n)+  x^(2n+2)/(1+eps*eps)^(n+1)

再逐项积分得到,

arctg(x)=x-x^3/3+x^5/5-…+(-1)^n*x^(2n+1)/(2n+1)+  x^(2n+3)/[(2n+3)(1+eps*eps)^(n+1)] (2)

2)的余项取绝对值,再取eps=0(保守估计),得到R(x)<=x^(2n+3)/(2n+3)<10^(-M) 3),10^(-M)就是我们需要达到的误差限,即十进制小数点后面M位。

如果选择pi=4arctg(1)来算pi,那R(x)<=1/(2n+3),速度太慢了,比如,如果要算到M=4位,那保守估计n需要取n=4999,实验一下,如下程序:

int main(int argc, char* argv[])

{

       int tag=0;

       double s=0;

       for(double i=4998.0;i>=0.0;i-=1.0)

       {

              if(tag)

              {

                     s-=4.0/(i*2+1.0);

                     tag=0;

              }

              else

              {

                     s+=4.0/(i*2+1.0);

                     tag=1;

              }

              printf("%5lf : %lf\n",i,s);

       }

       return 0;

}

最后输出的最后一部分:

10.000000 : 0.099953

9.000000 : -0.110573

8.000000 : 0.124721

7.000000 : -0.141946

6.000000 : 0.165747

5.000000 : -0.197890

4.000000 : 0.246555

3.000000 : -0.324874

2.000000 : 0.475126

1.000000 : -0.858207

0.000000 : 3.141793

可见收敛速度极慢,为什么第4位不是5?实际上第4位都不准确,这是因为公式是摆动的,最后的数据会在3.14133.1417之间,如果你要精确到5位,那就要扩大10倍的运算量,就是说这样的摆动会从5千次一直持续到5万次,然后才会稳定下来,如果要算更高位数,完全无法想像。甚至会觉得这样算比割圆还慢,祖冲之不是算到了第7位是6~7之间吗,他给出的密率是相当准确的,为我国的古代数学家感到自豪。

 

世界上有段时间是以各个国家计算圆周率的位数标志数学水平的,到了现代有人开始怀疑做这样的事是否有意义?现在的SuperPi可以用来测PC机上浮点数运算的能力,因为它会运用大量的FFT算法,浮点数运算量巨大。而目前的世界记录还是在日本,因为他有世界上性能最强的计算机,记录好像是1.2兆(亿上面一个单位)。以下一段摘自互联网:

 

“原苏联数学家格拉维夫斯基证明了pi的值即使算到100位已完全没有必要了.他算出,假设有一个球体,它的半径等于地球到天狼星的距离1.32*10^12公里,在这个球中装满了微生物,假定球的每1立方毫米中有10^10个微生物,然后把所有微生物排列在一条线上,使每两个相邻微生物的间距重新等于地球到天狼星的距离,那么,拿这个幻想长度来作为圆的直径,取pi的值们确到小数点后100位,可以算出这个巨圆的周长们确到1/1000000毫米以下.法国天文学家阿拉哥曾说过“无休止地追求的精确值,没有丝毫精确意义”.”

 

工程上的计算,现实主义上的计算,都不会要求这样高精度的pi,那计算pi就成了数学家的游戏?成了毫无意义的事吗?不完全是,计算pi的值本身虽然没有意义,但这个过程很有意义,算法的利用,计算机上的实现,展现计算机的性能等。

3)是余项式,如果arctg(x)中的x表现成1/m的形式,则余项将以线性收敛,这样的改进对效率有大幅的提高,最有名的一个公式是Machin公式,如前面所示,两项的和,对于整体的精度,可以取较小的一个,也就是5,保守误差是1/(5^(2n+3))(2n+3)<1/10^M,也就是25^n约为10^M,即算1项,将得到log(25)= 1.398(以10为底)位的十进制精度,于是要计算到M=10000位,那N要取7154

 

然后再思考高精度的问题。

如何做到高精度,计算机内的数据一般是定长位的,比如32位表示一个long整数,如果超出了最大范围怎么办?那就用64位的long long,如果是32位的计算机,那实际上也还是将64位拆成高32位和低32位的形式处理,计算机只能处理定长,如果要高精就必须牺牲速度,很简单的道理,算一个32位整数的加法和算一个3万位整数的加法显然是代价不同的,于是就只需要定义数据结构,和实现基本运算了。

 

考虑级数表达式,最好的情况是只有加减乘除,数据只有整数,没错Machin公式就是如此。加法减法和乘法不会丢失精度,而除法会丢失精度,比如7/3=2.333…它不可能用一个整数表示,就是说小数点怎么移都不会是整数,而你若取2.33或者2.34都不准确,因为它始终存在余数,余数是丢失精度的原因,所以要保存精度就是保存余数,在这里的除法将产生两个整数(相当于有理数),一个是商一个是余数,形如:

 

a / b = (d,r)

 

如果以10为基,那高精度无非就是对这样的除法继续做下去,将r*10恢复精度,再除,直到满足精度为止。想像这里的ab也是(d,r)的形式,再考查连除:

 

a/b/c=(d1,r1)/c=d1/c=(d2,r2)

 

恢复一次:r1*10/b=(d1,r1) -> (r2*10+d1)/c=(d2,r2)

 

可以证明是正确的,于是可以定义为:

 

(a,0)/b/c=(d1,r1)/c=(d2,r2)

 

对于每个数据用(d,r)保存,定义(d1,r1)/(b,0)=(r2*10+d1)/b=(d2,r2)(d,r)中的r表示前一次除得到的余数,所以在当前的除法中要先恢复余数,再加上被除数。

 

另外再用一个连除的技巧,如下一序列:

 

1/5 1/5^3 1/5^5 … (4)

 

是个等比数列,要计算得到结果,要进行多少次除法呢?如果每项都直接做除法,那就需要n*n量级的除法。连除就是把它们连起来除,前一个的结果除25得到后一个结果,总共只需要n次除法。

 

对比Machin公式,具体计算过程就是先算一遍(4),再将得到的序列分别除上1,3,5,…,再将结果正负交替地累加起来,如果取基是10000,那算一遍将可以得到十进制4位有效数,结果存到一个数组,全部算完后再整理一遍,因为有可能有的项为负,整理完就得到结果。

 

源程序是:

int main(int argc, char* argv[])

{

       int const N=7200;

       int const M=10000;

       int const B=10000;

       int const L=4;

       // Machin公式 计算pi到一万位

       int s[M/L];

       int r1[N]={0},r2[N]={0},d1[N]={0},d2;

       int r3[N]={0},r4[N]={0},d3[N]={0},d4;

       int i,k,t,p=0,mp=M/L/20;

 

       r1[0]=1;

       r1[1]=3;

       r3[0]=4;

 

       printf("正在计算,请等待\n____________________\n");

 

       for(k=0;k<M/L;++k)

       {

              t=r1[0]*B;

              d1[0]=t/0x5;

              r1[0]=t%0x5;

              //

              t=r3[0]*B;

              d3[0]=t/0xEF;

              r3[0]=t%0xEF;

              s[k]=d1[0]-d3[0];

              int tag=0;

              for(i=1;i<N;++i)

              {

                     t=r1[i]*B+d1[i-1];

                     d1[i]=t/0x19;

                     r1[i]=t%0x19;

                     t=r2[i]*B+d1[i];

                     d2=t/(2*i+1);

                     r2[i]=t%(2*i+1);

                     //

                     t=r3[i]*B+d3[i-1];

                     d3[i]=t/0xDF21;

                     r3[i]=t%0xDF21;

                     t=r4[i]*B+d3[i];

                     d4=t/(2*i+1);

                     r4[i]=t%(2*i+1);

                     if(tag)

                     {

                            s[k]+=(d2-d4);

                            tag=0;

                     }

                     else

                     {

                            s[k]+=(d4-d2);

                            tag=1;

                     }

              }

              if(p==mp)

              {

                     printf(">");

                     p=0;

              }

              else

                     p++;

       }

       for(i=M/L-1;i>=0;i--)

       {

              while(s[i]>=B)

              {

                     s[i-1]++;

                     s[i]-=B;

              }

              while(s[i]<0)

              {

                     s[i-1]--;

                     s[i]+=B;

              }

       }

       printf("\npi=3.\n");

       for(i=0;i<M/L;++i)

              printf("%04d",s[i]);

       return 0;

}

 

2、计算e(自然对数的底)

e有个形式简单,又收敛得比较快的级数,原形是由e^x的泰勒展开,

e=1+1+1/2+1/3!+…+1/n!+… (5)

由于阶乘跑得比指数快,所以会以超线性收敛,而且形式简单,便于编程。

 

在计算之前也做个技巧,就是把它们叠乘起来,将(5)改写成:

e=2+1/2(1+1/3(1+1/4(1+…1/(n-1)(1+1/n)…)))

 

从最里面的括号往外算,共做n次除法和加法得一段结果,运算效率也是O(N*M),但是由于收敛速度快些,所以N项节省一些,所以算一万位的e比上面的程序快很多。

 

源程序是:

#ifndef UINT

typedef unsigned int UINT;

#endif

int GetLength(int m)

{

       int n=1;

       double x=2.0/2.72;

       while(m)

       {

              while(x<1.0)

              {

                     x*=(double)(++n+1);

              }

              while(x>=1.0 && m)

              {

                     x/=10.0;

                     --m;

              }

       }

       while(x<1.0)

       {

              x*=(double)(++n+1);

       }

       return n;

}

int main(int argc, char* argv[])

{

       const UINT base=1000000;

       int i,k,m,n;

       printf("输入计算e的有效位数n=");

       scanf("%d",&n);

       m=GetLength(n);

       UINT *r=new UINT[m+1];

       assert(r!=NULL);

       for(i=0;i<=m;++i)

              r[i]=1;

       //memset(r,1,sizeof(unsigned int)*(m+1));

       printf("e=2.\n");

       for(k=n;k>0;k-=6)

       {

              UINT y=0;

              for(i=m;i>=2;--i)

              {

                     y=y+r[i]*base;

                     r[i]=y%i;

                     y/=i;

              }

              if(k<6)

              {

                     char str[128];

                     sprintf(str,"%06d",y%base);

                     str[k]='\0';

                     printf("%s\t\n",str);

              }

              else

              {

                     if(y<base)

                            printf("%06d\t",y);

                     else

                     {

                            printf("XXXXXX\t");

                            if(r)

                                   delete[]r;

                            return 1;

                     }

              }

       }

       if(r)

              delete[]r;

       return 0;

}

 

这里由于和式每一项的数据控制在较低精度,再由余数恢复的方法保存精度,再逐段展开,就可以不用专门设计高精运算库,实现较高精度的计算。如果对性能要求更高,有必要好好学一下高精算法,再用高级的收敛速度更快的公式运算,希望能获得更多的惊喜!

 

[附:pi值部分结果]

正在计算,请等待

____________________

>>>>>>>>>>>>>>>>>>> 

pi=3.

14159265358979323846264338327950288419716939937510582097494459230781640628620899

86280348253421170679821480865132823066470938446095505822317253594081284811174502

84102701938521105559644622948954930381964428810975665933446128475648233786783165

27120190914564856692346034861045432664821339360726024914127372458700660631558817

48815209209628292540917153643678925903600113305305488204665213841469519415116094

33057270365759591953092186117381932611793105118548074462379962749567351885752724

89122793818301194912983367336244065664308602139494639522473719070217986094370277

05392171762931767523846748184676694051320005681271452635608277857713427577896091

73637178721468440901224953430146549585371050792279689258923542019956112129021960

86403441815981362977477130996051870721134999999837297804995105973173281609631859

50244594553469083026425223082533446850352619311881710100031378387528865875332083

81420617177669147303598253490428755468731159562863882353787593751957781857780532

17122680661300192787661119590921642019893809525720106548586327886593615338182796

82303019520353018529689957736225994138912497217752834791315155748572424541506959

50829533116861727855889075098381754637464939319255060400927701671139009848824012

85836160356370766010471018194295559619894676783744944825537977472684710404753464

62080466842590694912933136770289891521047521620569660240580381501935112533824300

35587640247496473263914199272604269922796782354781636009341721641219924586315030

28618297455570674983850549458858692699569092721079750930295532116534498720275596

02364806654991198818347977535663698074265425278625518184175746728909777727938000

81647060016145249192173217214772350141441973568548161361157352552133475741849468……

 

阅读(25774) | 评论(5)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册