找回密码
 欢迎注册
查看: 124095|回复: 112

[擂台] 计算百万位e

[复制链接]
发表于 2008-4-16 19:59:25 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?欢迎注册

×
精华
e和$pi$一样,是数学分析中使用非常广泛的无理数之一。千百年来,有无数人投入到计算e和$pi$的计算工作中。相对于计算$pi$,计算e的算法较为简单,而且复杂度也低于计算$pi$。以Pifast 4.3为例,在我的电脑上(PIV2.6)计算100万位e值,仅需1.23秒,而计算100万$pi$则需要3.8秒。
   本文首先借助HugeCalc,探讨计算e的各种算法。当然大家也可以自己写个计算e的程序,比拼一下谁的程序很快,谁的算法更优。

  楼下使用的HugeCacl配置文件定义为:
[UserSetCPUID]
NumOfCores  =        1
SSE2Support =        1
CacheL1size =        8
CacheL2size =        512
CacheL3size =        0
Cacheburst  =        64

[ 本帖最后由 liangbch 于 2008-4-24 17:24 编辑 ]
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-16 20:02:02 | 显示全部楼层
我讨论一下最简单,最容易理解的算法。

  $e=1+ 1/1! + 1/2! + 1/3! + ... 1/n! $
  欲达到K位有效数字,需使得 $n!> 10^K$

算法描述
$ e=1+ 1/{1!} + 1/{2!} + 1/{3!} + ... 1/{n!} $
以n=5为例,我们将各项通分
  $e= 1/1$
  $+1/(1*2) $
  $+ 1/(1*2*3) $
  $+ 1/(1*2*3*4) $
  $+ 1/(1*2*3*4*5) $
  容易得到算法
  n=5,i=1, item=1, s=1;
  for (i=1;i<=5;i++)
  {
      item /=i ;
      s+=item;
  }
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-4-16 20:04:43 | 显示全部楼层
简单算法也不很慢吧? 不过是个O(n^2)算法
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-16 20:04:51 | 显示全部楼层
本帖给出使用HugeCalc库,使用楼上的算法,计算e的一个实现。该算法是一个O(n^2)的算法,在我的电脑上,计算10万位e需要7.61秒。
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <math.h>
  4. #include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeCalc.h" // 公共接口
  5. #include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeInt.h" // 10进制系统
  6. #pragma message( "automatic link to ../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
  7. #pragma comment( lib, "../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
  8. #define integer CHugeInt
  9. //返回计算d位有效数字所需的最大的n
  10. static UINT32 maxNForCalcE(int d)
  11. {
  12. double f=d*log(10);
  13. double r=0;
  14. UINT32 i=0;
  15. do
  16. {
  17. i++; r += log(i);
  18. }while (r<f);
  19. return i;
  20. }
  21. void calc_e_1(integer *p,int len)
  22. {
  23. integer item;
  24. integer r;
  25. UINT32 n=maxNForCalcE(len);
  26. UINT32 i;
  27. item=1L;
  28. item.DecLShift(len); //增加有效数字
  29. r=1L;
  30. for (i=1;i<=n;i++)
  31. {
  32. item /= i;
  33. r += item;
  34. }
  35. *p=r;
  36. }
  37. void calc_e1_main()
  38. {
  39. int d,len;
  40. integer e;
  41. char *pBuff=NULL;
  42. const char *pTmp;
  43. char fileName[32];
  44. FILE *fp=NULL;
  45. printf("calculate e (simple version)\n");
  46. printf("How many digital is needed?");
  47. scanf("%d",&d);
  48. len=(d+17)/9*9; //取9的倍数,可能稍微快一点
  49. HugeCalc::EnableTimer( TRUE );
  50. HugeCalc::ResetTimer();
  51. calc_e_1(&e,len);
  52. // 记录计算耗时
  53. UINT32 u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
  54. printf("计算用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
  55. //----------------------------------------
  56. HugeCalc::EnableTimer( TRUE );
  57. HugeCalc::ResetTimer();
  58. pBuff=new char[e.GetDigits()+3];
  59. if (pBuff==NULL)
  60. goto thisExit;
  61. pTmp= e.GetStr(FS_NORMAL);
  62. strcpy(pBuff+1,pTmp);
  63. pBuff[0]=pBuff[1];
  64. pBuff[1]='.'; //插入小数点
  65. pBuff[d+2]=0; //去掉多余的数字
  66. sprintf(fileName,"e1_%d.txt",d);
  67. fp=fopen(fileName,"wb");
  68. fwrite(pBuff,d+2,1,fp);
  69. fclose(fp); fp=NULL;
  70. u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
  71. printf("输出到文件用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
  72. thisExit:
  73. if (fp!=NULL)
  74. {
  75. fclose(fp); fp=NULL;
  76. }
  77. if (pBuff!=NULL)
  78. {
  79. delete[] pBuff; pBuff=NULL;
  80. }
  81. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-16 20:16:01 | 显示全部楼层
本帖将给出计算e的另一个算法,这个算法复杂度同4#,只是4楼主要使用大数除以单精度整数的算法,而该算法主要使用大数乘以单精度整数的算法。该算法比4#更快,在我的电脑上计算10万e值,需要5.88秒。
  
算法描述
$e=1+ 1/{1!} + 1/{2!} + 1/{3!} + ... 1/{n!}$
以n=5为例,我们将各项通分
$ e= {1xx2xx3xx4xx5}/{5!}+{2xx3xx4xx5}/{5!}+{3xx4xx5}/{5!}+{4xx5}/{5!}+5/{5!} + 1/{5!}$

调整各项顺序,并提取公分母得
$e=(1/{5!})xx ( 1+5+ 5xx4 + 5xx4xx3 + 5xx4xx3xx2 + 5xx4xx3xx2xx1)$
若 e 表示为分数p/q,则
$p = 1 +5 +5xx4 + 5xx4xx3 + 5xx4xx3xx2 + 5xx4xx3xx2xx1$
$q=5xx4xx3xx2 xx1$

容易得到算法
n=5
step1:q=1,p=1
step2:q *= n,p +=q ,n--
step3:如果n>0,转step2,否则转step4
step4:将分数 p/q化为小数,得到e
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-16 20:20:39 | 显示全部楼层
本帖给出使用HugeCalc,使用楼上算法计算e的一个实现。
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <math.h>
  4. #include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeCalc.h" // 公共接口
  5. #include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeInt.h" // 10进制系统
  6. #pragma message( "automatic link to ../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
  7. #pragma comment( lib, "../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
  8. #define integer CHugeInt
  9. //计算e的稍复杂的版本,用到大数加法,以及大数乘法和大数除法
  10. //该程序快于第一个版本,特别是要求精度较低时,优势更为明显。
  11. //欲达到n位有效数字,需使得 n!> 10^n
  12. //返回计算d位有效数字所需的最大的n
  13. static UINT32 maxNForCalcE(int d)
  14. {
  15. double f=d*log(10);
  16. double r=0;
  17. UINT32 i=0;
  18. do
  19. {
  20. i++; r += log(i);
  21. }while (r<f);
  22. return i;
  23. }
  24. void calc_e_2(integer *r,int len)
  25. {
  26. integer p,q;
  27. UINT32 n=maxNForCalcE(len);
  28. p=1; q=1;
  29. do
  30. {
  31. q*=n;
  32. p+=q;
  33. n--;
  34. }while (n>0);
  35. p.DecLShift( q.GetDigits()-1); //增加有效数字
  36. *r = p / q;
  37. }
  38. void calc_e2_main()
  39. {
  40. int d,len;
  41. integer e;
  42. char *pBuff=NULL;
  43. const char *pTmp;
  44. char fileName[32];
  45. FILE *fp=NULL;
  46. printf("calculate e (simple version2)\n");
  47. printf("How many digital is needed?");
  48. scanf("%d",&d);
  49. len=(d+17)/9*9; //取9的倍数,可能稍微快一点
  50. HugeCalc::EnableTimer( TRUE );
  51. HugeCalc::ResetTimer();
  52. calc_e_2(&e,len);
  53. // 记录计算耗时
  54. UINT32 u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
  55. printf("计算用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
  56. //----------------------------------------
  57. HugeCalc::EnableTimer( TRUE );
  58. HugeCalc::ResetTimer();
  59. pBuff=new char[e.GetDigits()+3];
  60. if (pBuff==NULL)
  61. goto thisExit;
  62. pTmp= e.GetStr(FS_NORMAL);
  63. strcpy(pBuff+1,pTmp);
  64. pBuff[0]=pBuff[1];
  65. pBuff[1]='.'; //插入小数点
  66. pBuff[d+2]=0; //去掉多余的数字
  67. sprintf(fileName,"e2_%d.txt",d);
  68. fp=fopen(fileName,"wb");
  69. fwrite(pBuff,d+2,1,fp);
  70. fclose(fp); fp=NULL;
  71. u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
  72. printf("输出到文件用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
  73. thisExit:
  74. if (fp!=NULL)
  75. {
  76. fclose(fp); fp=NULL;
  77. }
  78. if (pBuff!=NULL)
  79. {
  80. delete[] pBuff; pBuff=NULL;
  81. }
  82. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-16 20:35:28 | 显示全部楼层
本帖将给出一个快速的算法,这个算法能够充分利用大数乘法,计算速度比前两个算法快的多(在PIV2.6上,计算10万位e需0.66秒,计算100万位e需要8.35秒)

算法详述:
$e=1+1/{1!} + 1/{2!} + 1/{3!}+ 1/{4!} + ... 1/{N!}$ (一)
现在我们将讨论如何化解
$ 1/{1!} + 1/{2!} + 1/{3!}+ 1/{4!} + ... 1/{N!}$ (二)
   为$p/q$
以计算 $ 1/{1!} + 1/{2!} + 1/{3!} + 1/{4!} +1/{5!} + 1/{6!} + 1/{7!} + 1/{8!}$ 为例

将这个多项式分成2部分得
  第一部分为:$( 1/{0!}) xx( 1/1 + 1/{1xx2} + 1/{1xx2xx3}+1/{1xx2xx3xx4})$
  第二部分为:$1/{4!}(1/5+1/{5xx6}+1/{5xx6xx7}+1/{5xx6xx7xx8})$
  暂不考虑系数$1/{0!}$和$1/{4!}$,则问题转化为求
    $1/n+ 1/{nxx(n+1)}+1/{n(n+1)(n+2)} ...+1/{n(n+1)(n+2)...m}$ (三)
   的形式。易看出,若这个多项的的计算结果为$ p/q $,则 $q=nxx(n+1)xx(n+2)xx...(m) $
  
  当m-n较小时,我们可以按照calc_Frac_Sum中的方法直接计算,
   
  当m-n较大时,我们可以将这个多项式再分成2段,
   一段为从n 到 (n+m)/2,另一端为一段为从(n+m)/2+1 到 m
  令r=(n+m)/2、s=(n+m)/2+1
  为了计算(三),我们先分别计算
  $1/n + 1/{(n(n+1))} + ...1/{(n(n+1)...r)}$ (四)
    $1/s + 1/{(s(s+1))} + ...1/{(s(s+1)...m)} $(五)
  
  令  (三)的计算结果为$p/q$,
      (四)的计算结果为${p1}/{q1}$,
      (五)的计算结果为${p2}/{q2}$,
   
   则:$1/n+ 1/{nxx(n+1)}+1/{n(n+1)(n+2)}+...+1/{n(n+1)(n+2)...r}$
    $+1/{n(n+1)(n+2)...s} + ... + 1/{n(n+1)(n+2)...m}$
    $= p/q$
    $= {p1}/{q1} + (1/{nxx(n+1)xx(n+2)xx..r}) xx {p2}/{q2}$
    $= {p1}/{q1} + 1/{q1} xx {p2}/{q2}$
    $= {p1xxq2}/{q1xxq2} + {p2}/{q1xxq2}$
    $= {p1xxq2+p2}/{q1xxq2}$
  所以:$p=p1xxq2+p2, q=q1xxq2$
  
下面,我们以$(1/5+1/{5xx6} + 1/{5xx6xx7} + 1/{5xx6xx7xx8})$ 说明计算过程:
$ p/q= 1/5 + 1/{5xx6} + 1/{5xx6xx7} + 1/{5xx6xx7xx8}$
$= (1/5 + 1/{5xx6}) + (1/{5xx6xx7} + 1/{5xx6xx7xx8})$
$= ((6+1)/(5xx6)) + 1/(5xx6) xx ( 1/7 + 1/{7xx8} )$
$= ((6+1)/(5xx6)) + 1/(5xx6) xx ( {8+1}/{7xx8} ) $
$= (7/30) + 1/(30) xx ( 9/56)$
$= (7 xx 56 + 9 )/(30xx56)$
  按此法计算,使用递归方式,所有的多项式终将合并,这时n=1,m=N,因此(二)将变为(三)
  则有e
$=1+1/{1!}+1/{2!}+1/{3!} + ...1/{N!}$
$=1+p/q$
$ =(p+q)/q$
  最后我们作一次除法,将分数(p+q)/q化为小数即可
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-4-16 20:38:13 | 显示全部楼层
看不懂哦
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-16 21:31:16 | 显示全部楼层
本帖给出使用HugeCalc,使用7#楼算法计算e的一个实现,使用该程序,在PIV2.6上,计算10万位e需0.412秒,计算100万位e需要5.06秒。
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <math.h>
  4. #include "../../../HugeCalc_API/CppAPI/Include/HugeCalc.h" // 公共接口
  5. #include "../../../HugeCalc_API/CppAPI/Include/HugeInt.h" // 10进制系统
  6. #pragma message( "automatic link to ../../../HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
  7. #pragma comment( lib, "../../../HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
  8. #define integer CHugeInt
  9. #define USE_HUGE_CALC_DIV
  10. #if 0
  11. # define MODIFIED_BY_GxQ
  12. #endif
  13. #define MIN_SPLIT 32
  14. static DWORD g_divTime=0;
  15. // 计算e的一个快速的版本,采用2分法,尽量使用以及大数乘法,
  16. // 收尾阶段采用大数除法
  17. //欲达到n位有效数字,需使得 n!> 10^n
  18. //返回计算d位有效数字所需的最大的n
  19. static UINT32 maxNForCalcE(int d)
  20. {
  21. double f=d*log(10);
  22. double r=0;
  23. UINT32 i=0;
  24. //loop untill log(n!)>log(10^d)
  25. do
  26. {
  27. i++; r += log(i);
  28. }while (r<f);
  29. return i;
  30. }
  31. // calc 1/n1 + 1/(n1*(n1+1)) + 1/(n1*(n1+1)+(n1+2))+ 1/(n1*(n1+1)+(n1+2)+...m)
  32. // 计算结果为 p/q
  33. // for example:
  34. // 1/6 + 1/6*7 + 1/6*7*8 + 1/6*7*8*9
  35. // = 7*8*9/6*7*8*9 + 8*9/6*7*8*9 + 9/6*7*8*9 + 1/ 6*7*8*9
  36. // calculate
  37. // q=6*7*8*9, p=1 + 9+ 8*9 + 7*8*9
  38. // steps:
  39. // step1: p=1, q=1;
  40. // step2: q*=9, p+=q , q=9, q=1+9
  41. // step3: q*=8, p+=q , q=9*8, q=1+8*9
  42. // step4: q*=7, p+=q , q=9*8*7, q=1+ 8*9 + 7*8*9
  43. // step5: q*=6
  44. static void calc_Frac_Sum(UINT32 n1,UINT32 n2, integer &p,integer &q)
  45. {
  46. p=(UINT32)1;
  47. q=(UINT32)1;
  48. for ( UINT32 i=n2;i>n1;i--)
  49. {
  50. q*=i;
  51. p+=q;
  52. }
  53. q*=i;
  54. }
  55. #ifndef MODIFIED_BY_GxQ
  56. void splitCalc(UINT32 n1,UINT32 n2, integer &p,integer &q)
  57. {
  58. if (n2-n1<=MIN_SPLIT)
  59. {
  60. calc_Frac_Sum(n1,n2,p,q);
  61. }
  62. else
  63. {
  64. integer p1,q1,p2,q2;
  65. splitCalc( n1, (n1+n2)/2, p1,q1);
  66. splitCalc( (n1+n2+2)/2 ,n2, p2,q2);
  67. p=p1*q2 + p2;
  68. q=q1*q2;
  69. }
  70. }
  71. #else
  72. integer tmp;
  73. void splitCalc(UINT32 n1,UINT32 n2, integer &p,integer &q)
  74. {
  75. if (n2-n1<=MIN_SPLIT)
  76. {
  77. calc_Frac_Sum(n1,n2,p,q);
  78. }
  79. else
  80. {
  81. integer q1,p2;
  82. splitCalc( n1, (n1+n2)/2, p, q1);
  83. splitCalc( (n1+n2+2)/2 ,n2, p2,q );
  84. // ( p *= q ) += p2;
  85. tmp.Swap( p );
  86. p.Mul( tmp, q ) += p2;
  87. // q *= q1;
  88. tmp.Swap( q );
  89. q.Mul( tmp, q1 );
  90. }
  91. }
  92. #endif
  93. void calc_e_3(integer *r, int len )
  94. {
  95. UINT32 n;
  96. int shift;
  97. integer p,q;
  98. n= maxNForCalcE(len)+2;
  99. splitCalc(1,n,p,q); // p/q= 1/1!+1/2!+1/3!+1/4!+...
  100. p+=q; // p/q +=1 // e= p/q= 1 + 1/1!+1/2!+1/3!+1/4!+...
  101. shift=(len+8)/9*9; //get upbounds get times of 9
  102. p.DecLShift(shift); //增加有效数字
  103. g_divTime=GetTickCount();
  104. #ifdef USE_HUGE_CALC_DIV
  105. #ifndef MODIFIED_BY_GxQ
  106. p=p /q;
  107. #else
  108. p /= q;
  109. #endif
  110. #else
  111. {
  112. void inverse( const integer *s, integer *pResult,int len);
  113. integer t;
  114. inverse(&q,&t,len);
  115. p *=t;
  116. }
  117. #endif
  118. g_divTime=GetTickCount()-g_divTime;
  119. *r=p;
  120. }
  121. void calc_e3_main()
  122. {
  123. int len;
  124. integer e;
  125. char *pBuff=NULL;
  126. const char *pTmp;
  127. char fileName[32];
  128. FILE *fp=NULL;
  129. printf("calculate e (fast version1)\n");
  130. #ifdef MODIFIED_BY_GxQ
  131. printf("defined 'MODIFIED_BY_GxQ'\n");
  132. #endif
  133. while (true)
  134. {
  135. printf("How many digital is needed(0 will exit)?");
  136. scanf("%d",&len);
  137. if (len==0)
  138. break;
  139. HugeCalc::EnableTimer( TRUE );
  140. HugeCalc::ResetTimer();
  141. calc_e_3(&e,len);
  142. // 记录计算耗时
  143. UINT32 u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
  144. printf("计算用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
  145. if (g_divTime>=2)
  146. {
  147. printf("其中除法用时 %f秒\n", (double)g_divTime/1000.00);
  148. }
  149. //----------------------------------------
  150. HugeCalc::EnableTimer( TRUE );
  151. HugeCalc::ResetTimer();
  152. pBuff=new char[e.GetDigits()+3];
  153. if (pBuff==NULL)
  154. goto thisExit;
  155. pTmp= e.GetStr(FS_NORMAL);
  156. strcpy(pBuff+1,pTmp);
  157. pBuff[0]=pBuff[1];
  158. pBuff[1]='.'; //插入小数点
  159. pBuff[len+2]=0; //去掉多余的数字
  160. sprintf(fileName,"e3_%d.txt",len);
  161. FILE *fp=fopen(fileName,"wb");
  162. fwrite(pBuff,len+2,1,fp);
  163. fclose(fp); fp=NULL;
  164. delete[] pBuff; pBuff=NULL;
  165. u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
  166. printf("输出到文件用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
  167. }
  168. thisExit:
  169. if (fp!=NULL)
  170. {
  171. fclose(fp); fp=NULL;
  172. }
  173. if (pBuff!=NULL)
  174. {
  175. delete[] pBuff; pBuff=NULL;
  176. }
  177. }
复制代码
[ 本帖最后由 liangbch 于 2008-4-17 14:34 编辑 ]
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-4-16 21:37:11 | 显示全部楼层
我想 我想 是不是有更精彩更复杂的算法 比如类似阶乘的?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

小黑屋|手机版|数学研发网 ( 苏ICP备07505100号 )

GMT+8, 2024-11-21 18:06 , Processed in 0.029416 second(s), 17 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表