liangbch 发表于 2008-4-16 19:59:25

计算百万位e

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

楼下使用的HugeCacl配置文件定义为:

NumOfCores=        1
SSE2Support =        1
CacheL1size =        8
CacheL2size =        512
CacheL3size =        0
Cacheburst=        64

[ 本帖最后由 liangbch 于 2008-4-24 17:24 编辑 ]

liangbch 发表于 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)算法

liangbch 发表于 2008-4-16 20:04:51

本帖给出使用HugeCalc库,使用楼上的算法,计算e的一个实现。该算法是一个O(n^2)的算法,在我的电脑上,计算10万位e需要7.61秒。#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeCalc.h"        // 公共接口
#include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeInt.h"        // 10进制系统

#pragma message( "automatic link to ../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
#pragma comment( lib, "../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )

#define integer CHugeInt

//返回计算d位有效数字所需的最大的n
static UINT32 maxNForCalcE(int d)
{
        double f=d*log(10);
        double r=0;
        UINT32 i=0;
        do
        {
                i++;        r += log(i);
        }while (r<f);
        return i;
}

void calc_e_1(integer *p,int len)
{
        integer item;
        integer r;
        UINT32 n=maxNForCalcE(len);
        UINT32 i;
       
        item=1L;
        item.DecLShift(len); //增加有效数字

        r=1L;
        for (i=1;i<=n;i++)
        {
                item /= i;
                r += item;
        }
        *p=r;
}

void calc_e1_main()
{
        int d,len;
        integer e;
        char *pBuff=NULL;
        const char *pTmp;
        char fileName;
        FILE *fp=NULL;

        printf("calculate e (simple version)\n");
        printf("How many digital is needed?");
        scanf("%d",&d);
        len=(d+17)/9*9; //取9的倍数,可能稍微快一点

        HugeCalc::EnableTimer( TRUE );
        HugeCalc::ResetTimer();

        calc_e_1(&e,len);

        // 记录计算耗时
        UINT32 u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
        printf("计算用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);

        //----------------------------------------
        HugeCalc::EnableTimer( TRUE );
        HugeCalc::ResetTimer();

        pBuff=new char;
        if (pBuff==NULL)
                goto thisExit;

        pTmp= e.GetStr(FS_NORMAL);
        strcpy(pBuff+1,pTmp);
       
        pBuff=pBuff;
        pBuff='.';        //插入小数点
        pBuff=0;        //去掉多余的数字

        sprintf(fileName,"e1_%d.txt",d);
        fp=fopen(fileName,"wb");
        fwrite(pBuff,d+2,1,fp);
        fclose(fp);        fp=NULL;

        u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
        printf("输出到文件用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);

thisExit:
        if (fp!=NULL)
        {
                fclose(fp); fp=NULL;
        }
        if (pBuff!=NULL)
        {
                delete[] pBuff;        pBuff=NULL;
        }
}

liangbch 发表于 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

liangbch 发表于 2008-4-16 20:20:39

本帖给出使用HugeCalc,使用楼上算法计算e的一个实现。#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeCalc.h"        // 公共接口
#include "../../HugeCalc/HugeCalc_API/CppAPI/Include/HugeInt.h"        // 10进制系统

#pragma message( "automatic link to ../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
#pragma comment( lib, "../../HugeCalc/HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )


#define integer CHugeInt

//计算e的稍复杂的版本,用到大数加法,以及大数乘法和大数除法
//该程序快于第一个版本,特别是要求精度较低时,优势更为明显。

//欲达到n位有效数字,需使得 n!> 10^n
//返回计算d位有效数字所需的最大的n
static UINT32 maxNForCalcE(int d)
{
        double f=d*log(10);
        double r=0;
        UINT32 i=0;
        do
        {
                i++;        r += log(i);
        }while (r<f);
        return i;
}

void calc_e_2(integer *r,int len)
{
        integer p,q;
        UINT32 n=maxNForCalcE(len);
       
        p=1; q=1;
        do
        {
                q*=n;
                p+=q;
                n--;
        }while (n>0);
       
        p.DecLShift( q.GetDigits()-1); //增加有效数字
        *r = p / q;
}

void calc_e2_main()
{
        int d,len;
        integer e;
        char *pBuff=NULL;
        const char *pTmp;
        char fileName;
        FILE *fp=NULL;

        printf("calculate e (simple version2)\n");
        printf("How many digital is needed?");
        scanf("%d",&d);
        len=(d+17)/9*9; //取9的倍数,可能稍微快一点

        HugeCalc::EnableTimer( TRUE );
        HugeCalc::ResetTimer();

        calc_e_2(&e,len);

        // 记录计算耗时
        UINT32 u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
        printf("计算用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);

        //----------------------------------------
        HugeCalc::EnableTimer( TRUE );
        HugeCalc::ResetTimer();

        pBuff=new char;
        if (pBuff==NULL)
                goto thisExit;

        pTmp= e.GetStr(FS_NORMAL);
        strcpy(pBuff+1,pTmp);
       
        pBuff=pBuff;
        pBuff='.';        //插入小数点
        pBuff=0;        //去掉多余的数字

        sprintf(fileName,"e2_%d.txt",d);
       
        fp=fopen(fileName,"wb");
        fwrite(pBuff,d+2,1,fp);
        fclose(fp);        fp=NULL;

        u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
        printf("输出到文件用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);

thisExit:
        if (fp!=NULL)
        {
                fclose(fp); fp=NULL;
        }
        if (pBuff!=NULL)
        {
                delete[] pBuff;        pBuff=NULL;
        }
}

liangbch 发表于 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

看不懂哦

liangbch 发表于 2008-4-16 21:31:16

本帖给出使用HugeCalc,使用7#楼算法计算e的一个实现,使用该程序,在PIV2.6上,计算10万位e需0.412秒,计算100万位e需要5.06秒。#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include "../../../HugeCalc_API/CppAPI/Include/HugeCalc.h"        // 公共接口
#include "../../../HugeCalc_API/CppAPI/Include/HugeInt.h"        // 10进制系统

#pragma message( "automatic link to ../../../HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )
#pragma comment( lib, "../../../HugeCalc_API/CppAPI/Lib/HugeCalc.lib" )


#define integer CHugeInt
#define USE_HUGE_CALC_DIV

#if 0
#        define MODIFIED_BY_GxQ
#endif

#define MIN_SPLIT 32
static DWORD g_divTime=0;

// 计算e的一个快速的版本,采用2分法,尽量使用以及大数乘法,
// 收尾阶段采用大数除法


//欲达到n位有效数字,需使得 n!> 10^n
//返回计算d位有效数字所需的最大的n

static UINT32 maxNForCalcE(int d)
{
        double f=d*log(10);
        double r=0;
        UINT32 i=0;
        //loop untill log(n!)>log(10^d)
        do
        {
                i++; r += log(i);
        }while (r<f);
        return i;
}

// calc 1/n1 + 1/(n1*(n1+1)) + 1/(n1*(n1+1)+(n1+2))+ 1/(n1*(n1+1)+(n1+2)+...m)
// 计算结果为 p/q
// for example:

// 1/6 + 1/6*7 + 1/6*7*8 + 1/6*7*8*9
// = 7*8*9/6*7*8*9 + 8*9/6*7*8*9 + 9/6*7*8*9 + 1/ 6*7*8*9
// calculate
//    q=6*7*8*9, p=1 + 9+ 8*9 + 7*8*9
// steps:
// step1: p=1, q=1;
// step2: q*=9, p+=q   ,q=9,   q=1+9
// step3: q*=8, p+=q   ,q=9*8,   q=1+8*9
// step4: q*=7, p+=q   ,q=9*8*7, q=1+ 8*9 + 7*8*9
// step5: q*=6
static void calc_Frac_Sum(UINT32 n1,UINT32 n2, integer &p,integer &q)
{
        p=(UINT32)1;
        q=(UINT32)1;
        for ( UINT32 i=n2;i>n1;i--)
        {
                q*=i;
                p+=q;
        }
        q*=i;
}

#ifndef MODIFIED_BY_GxQ
void splitCalc(UINT32 n1,UINT32 n2, integer &p,integer &q)
{
        if (n2-n1<=MIN_SPLIT)
        {
                calc_Frac_Sum(n1,n2,p,q);
        }
        else
        {
                integer p1,q1,p2,q2;
                splitCalc( n1, (n1+n2)/2,   p1,q1);
                splitCalc( (n1+n2+2)/2 ,n2, p2,q2);
                p=p1*q2 + p2;
                q=q1*q2;
        }
}
#else
integer tmp;
void splitCalc(UINT32 n1,UINT32 n2, integer &p,integer &q)
{
        if (n2-n1<=MIN_SPLIT)
        {
                calc_Frac_Sum(n1,n2,p,q);
        }
        else
        {
                integer q1,p2;
                splitCalc( n1, (n1+n2)/2,   p, q1);
                splitCalc( (n1+n2+2)/2 ,n2, p2,q );

                // ( p *= q ) += p2;
                tmp.Swap( p );
                p.Mul( tmp, q ) += p2;

                // q *= q1;
                tmp.Swap( q );
                q.Mul( tmp, q1 );
        }
}
#endif

void calc_e_3(integer *r, int len )
{
        UINT32 n;
        int shift;
        integer p,q;

        n= maxNForCalcE(len)+2;
        splitCalc(1,n,p,q);// p/q= 1/1!+1/2!+1/3!+1/4!+...

        p+=q;// p/q +=1   // e= p/q= 1 + 1/1!+1/2!+1/3!+1/4!+...
        shift=(len+8)/9*9;//get upbounds get times of 9
        p.DecLShift(shift); //增加有效数字

        g_divTime=GetTickCount();
#ifdef USE_HUGE_CALC_DIV
#ifndef MODIFIED_BY_GxQ
        p=p /q;
#else
        p /= q;
#endif
#else
        {
                void inverse( const integer *s, integer *pResult,int len);
                integer t;
                inverse(&q,&t,len);
                p *=t;
        }
#endif
        g_divTime=GetTickCount()-g_divTime;
        *r=p;
}

void calc_e3_main()
{
        int len;
        integer e;
        char *pBuff=NULL;
        const char *pTmp;
        char fileName;
        FILE *fp=NULL;

        printf("calculate e (fast version1)\n");
#ifdef MODIFIED_BY_GxQ
        printf("defined 'MODIFIED_BY_GxQ'\n");
#endif
        while (true)
        {
                printf("How many digital is needed(0 will exit)?");
                scanf("%d",&len);
                if (len==0)
                        break;

                HugeCalc::EnableTimer( TRUE );
                HugeCalc::ResetTimer();

                calc_e_3(&e,len);

                // 记录计算耗时
                UINT32 u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
                printf("计算用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
                if (g_divTime>=2)
                {
                        printf("其中除法用时 %f秒\n", (double)g_divTime/1000.00);
                }
                //----------------------------------------
                HugeCalc::EnableTimer( TRUE );
                HugeCalc::ResetTimer();

                pBuff=new char;
                if (pBuff==NULL)
                        goto thisExit;

                pTmp= e.GetStr(FS_NORMAL);
                strcpy(pBuff+1,pTmp);

                pBuff=pBuff;
                pBuff='.'; //插入小数点
                pBuff=0; //去掉多余的数字

                sprintf(fileName,"e3_%d.txt",len);
                FILE *fp=fopen(fileName,"wb");
                fwrite(pBuff,len+2,1,fp);
                fclose(fp); fp=NULL;
                delete[] pBuff; pBuff=NULL;

                u32Timer_Calc = HugeCalc::GetTimer( TIMER_UNIT_us );
                printf("输出到文件用时 %.6f 秒\n", (double)u32Timer_Calc/1000000.0);
        }

thisExit:
        if (fp!=NULL)
        {
                fclose(fp); fp=NULL;
        }
        if (pBuff!=NULL)
        {
                delete[] pBuff; pBuff=NULL;
        }
}

[ 本帖最后由 liangbch 于 2008-4-17 14:34 编辑 ]

无心人 发表于 2008-4-16 21:37:11

:)

我想
我想
是不是有更精彩更复杂的算法

比如类似阶乘的?
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 计算百万位e