近似计算之二

 

在《阶乘之计算从入门到精通-近似计算之一》中,我们采用两个数来表示中间结果,使得计算的范围扩大到1千万,并可0.02秒内完成10000000!的计算。在保证接近16位有效数字的前提下,有没有更快的算法呢。很幸运,有一个叫做Stirling的公式,它可以快速计算出一个较大的数的阶乘,而且数越大,精度越高。有http://mathworld.wolfram.com查找Stirling’s Series可找到2个利用斯特林公式求阶乘的公式,一个是普通形式,一个是对数形式。前一个公式包含一个无穷级数和的形式,包含级数前5项的公式为n!=sqrt(2*PI*n)*(n/e)n*(1+ 1/12/n+ 1/288/n2 –139/51840/n3-571/2488320/n4+…),这里的PI为圆周率,e为自然对数的底。后一个公式也为一个无穷级数和的形式,一般称这个级数为斯特林(Stirling)级数,包括级数前3项的n!的对数公式为:ln(n!)=0.5*ln(2*PI)+(n+0.5)*ln(n)-n+(1/12/n -1/360/n3 + 1/1260/n5)-…,下面我们分别用这两个公式计算n!.

 

完整的代码如下:

 

 

#include "stdafx.h"

#include "math.h"

#define PI 3.1415926535897932384626433832795

#define E 2.7182818284590452353602874713527

 

struct bigNum

{

double n; //科学计数法表示的尾数部分

int e; //科学计数法表示的指数部分,若一个bigNumx,x=n * 10^e

};

 

const double a1[]=

{ 1.00, 1.0/12.0, 1.0/288, -139/51840,-571.0/2488320.0 };

 

 

const double a2[]=

{ 1.0/12.0, -1.0/360, 1.0/1260.0 };

 

void printfResult(struct bigNum *p,char buff[])

{

while (p->n >=10.00 )

{p->n/=10.00; p->e++;}

sprintf(buff,"%.14fe%d",p->n,p->e);

}

 

// n!=sqrt(2*PI*n)*(n/e)^n*(1+ 1/12/n+ 1/288/n^2 -139/51840/n^3-571/2488320/n^4+…)

void calcFac1(struct bigNum *p,int n)

{

double logx,

s, //级数的和

item; //级数的每一项   

int i;

 

// x^n= pow(10,n * log10(x));

logx= n* log10((double)n/E);

p->e= (int)(logx); p->n= pow(10.0, logx-p->e);

p->n *= sqrt( 2* PI* (double)n);

 

 

//(1+ 1/12/n+ 1/288/n^2 -139/51840/n^3-571/2488320/n^4+)

for (item=1.0,s=0.0,i=0;i<sizeof(a1)/sizeof(double);i++)

{

s+= item * a1[i];

item /= (double)n; //item= 1/(n^i)

}

p->n *=s;

}

 

 

//ln(n!)=0.5*ln(2*PI)+(n+0.5)*ln(n)-n+(1/12/n -1/360/n^3 + 1/1260/n^5 -...)

void calcFac2(struct bigNum *p,int n)

{

double logR;

double s, //级数的和

item; //级数的每一项

int i;

 

logR=0.5*log(2.0*PI)+((double)n+0.5)*log(n)-(double)n;

 

// s= (1/12/n -1/360/n^3 + 1/1260/n^5)

for (item=1/(double)n,s=0.0,i=0;i<sizeof(a2)/sizeof(double);i++)

{

s+= item * a2[i];

item /= (double)(n)* (double)n; //item= 1/(n^(2i+1))

}

logR+=s;

 

//根据换底公式,log10(n)=ln(n)/ln(10)

p->e = (int)( logR / log(10));

p->n = pow(10.00, logR/log(10) - p->e);

}

 

 

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

{

struct bigNum r;

char buff[32];

int n;

printf("n=?");

scanf("%d",&n);

 

calcFac1(&r,n); //用第一种方法,计算n的阶乘

printfResult(&r,buff); //将结果转化一个字符串

printf("%d!=%s by method 1/n",n,buff);

 

calcFac2(&r,n); //用第二种方法,计算n的阶乘

printfResult(&r,buff); //将结果转化一个字符串

printf("%d!=%s by method 2/n",n,buff);

 

return 0;

}

程序运行时间的测量

 

算法的好坏有好多评价指标,其中一个重要的指标是时间复杂度。如果两个程序完成一个同样的任务,即功能相同,处理的数据相同,那么运行时间较短者为优。操作系统和库函数一般都提供了对时间测量的函数,这么函数一般都会返回一个代表当前时间的数值,通过在运行某个程序或某段代码之前调用一次时间函数来得到一个数值,在程序或者代码段运行之后再调用一次时间函数来得到另一个数值,将后者减去前者即为程序的运行时间。

 在windwos平台(指windwow95及以后的版本,下同),常用的用于测量时间的函数或方法有三种:1.API函数GetTickCount或C函数clock, 2.API函数QueryPerformanceCounter, 3:汇编指令RDSTC

 

1.API函数GetTickCount:

函数原形:DWORD GetTickCount(VOID);

该函数取回从电脑开机至现在的毫秒数,即每个时间单位为1毫秒。他的分辨率比较低,常用在测量用时较长程序,如果你的程序用时为100毫秒以上,可以使用这个函数.另一个和GetTickCount类似的函数是clock,该函数的回的时间的单位的是CLOCKS_PER_SEC,在windows95/2000操作系统,该值是1000,也就是说,在windows平台,这两个函数的功能几乎完全相同。

 

2.API函数QueryPerformanceCounter:

函数原形:BOOL QueryPerformanceCounter( LARGE_INTEGER *lpPerformanceCount);该函数取回当前的高分辨值performance计数器,用一个64bit数来表示。如果你的硬件不支持高分辨performance计数器,系统可能返加零。不像GetTickCount,每个计数器单位表示一个固定的时间1毫秒,为了知道程序确切的执行时间,你需要调用函数QueryPerformanceFrequency来得到每秒performance计数器的次数,即频率。

 

3.汇编指令RDTSC:

RDTSC 指令读取CPU内部的“时间戳(TimeStamp)",它是一个64位无符号数计数器,这条指令执行完毕后,存储在EDX:EAX寄存中。该指令从intel奔腾CPU开始引入,一些老式的CPU不支持该指令,奔腾后期的CPU包括AMD的CPU均支持这条指令。和QueryPerformanceCounter类似,要想知道程序的确实的执行时间,必须知道CPU的频率,即平常所说的CPU的主频。不幸的是没有现成的函数可以得到CPU的频率。一种办法可行的办法延时一段指定时间,时间的测量可以用QueryPerformanceCounter来做,在这段时间的开始和结束调用RDTSC,得到其时钟数的差值,然后除以这段时间的的秒数就可以了。

 

下面的代码给出使用3个函数封装和测试代码,用RDTSC指令来计时的代码参考了一个Ticktest的源代码,作者不详。

getTime1,使用GetTickCount返回一个表示当前时间的值,单位秒。

getTime2,和getTime1类似,精度更高。

getTime3,返回一个64bit的一个计数器,欲转换为秒,需除以CPU频率。示例代码见函数test3.

 

#include "stdafx.h"

#include "windows.h"

#include "tchar.h"

 

double getTime1()

{

DWORD t=GetTickCount();

return (double)t/1000.00;

}

 

double getTime2() //使用高精度计时器

{

static LARGE_INTEGER s_freq;

LARGE_INTEGER performanceCount;

double t;

 

if (s_freq.QuadPart==0)

{

if ( !QueryPerformanceFrequency( &s_freq))

return 0;

}

 

QueryPerformanceCounter( &performanceCount );

 

t=(double)performanceCount.QuadPart / (double)s_freq.QuadPart;

return t;

}

 

void test1()

{

double t1,t2;

t1=getTime1();

Sleep(1000);

t2=getTime1()-t1;

printf("It take %.8f second/n",t2);

}

 

void test2()

{

double t1,t2;

t1=getTime2();

Sleep(1000);

t2=getTime2()-t1;

printf("It take %.8f second/n",t2);

}

 

inline BOOL isNTOS() //检测是否运行在NT操作系统

{

typedef BOOL (WINAPI *lpfnGetVersionEx) (LPOSVERSIONINFO);

static int bIsNT=-1;

if (bIsNT!=1)

return (BOOL)bIsNT;

 

// Get Kernel handle

HMODULE hKernel32 = GetModuleHandle(_T("KERNEL32.DLL"));

if (hKernel32 == NULL)

return FALSE;

#ifdef _UNICODE

lpfnGetVersionEx lpGetVersionEx = (lpfnGetVersionEx) GetProcAddress(hKernel32, _T("GetVersionExW"));

#else

lpfnGetVersionEx lpGetVersionEx = (lpfnGetVersionEx) GetProcAddress(hKernel32, _T("GetVersionExA"));

#endif

 

if (lpGetVersionEx)

{

OSVERSIONINFO osvi;

memset(&osvi, 0, sizeof(OSVERSIONINFO));

osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

if (!lpGetVersionEx(&osvi))

bIsNT=FALSE;

else

bIsNT=(osvi.dwPlatformId == VER_PLATFORM_WIN32_NT);

}

else

{

//Since GetVersionEx is not available we known that

//we are running on NT 3.1 as all modern versions of NT and

//any version of Windows 95/98 support GetVersionEx

bIsNT=TRUE;

}

return bIsNT;

}

 

 

inline static BOOL checkRDSTC() //检测CPU是否支持RDSTC指令

{

static int bHasRDSTC= -1;

SYSTEM_INFO sys_info;

 

if ( bHasRDSTC !=-1 )

return (BOOL)bHasRDSTC;

 

GetSystemInfo(&sys_info);

if (sys_info.dwProcessorType==PROCESSOR_INTEL_PENTIUM)

{

try

{

_asm

{

_emit 0x0f ; rdtsc

_emit 0x31

}

}

catch (...) // Check to see if the opcode is defined.

{

bHasRDSTC=FALSE; return FALSE;

}

// Check to see if the instruction ticks accesses something that changes.

volatile ULARGE_INTEGER ts1,ts2;

_asm

{

xor eax,eax

_emit 0x0f ; cpuid

_emit 0xa2

_emit 0x0f ; rdtsc

_emit 0x31

mov ts1.HighPart,edx

mov ts1.LowPart,eax

xor eax,eax

_emit 0x0f ; cpuid

_emit 0xa2

_emit 0x0f ; rdtsc

_emit 0x31

mov ts2.HighPart,edx

mov ts2.LowPart,eax

}

// If we return true then there's a very good chance it's a real RDTSC instruction!

if (ts2.HighPart>ts1.HighPart)

bHasRDSTC=TRUE;

else if (ts2.HighPart==ts1.HighPart && ts2.LowPart>ts1.LowPart)

bHasRDSTC=TRUE;

else

{

printf("RDTSC instruction NOT present./n");

bHasRDSTC=FALSE;

}

}

else

bHasRDSTC=FALSE;

return bHasRDSTC;

}

 

//***********************************************

void getTime3( LARGE_INTEGER *pTime) //返加当前CPU的内部计数器

{

if (checkRDSTC())

{

volatile ULARGE_INTEGER ts;

 

//on NT don't bother disabling interrupts as doing

//so will generate a priviledge instruction exception

if (!isNTOS())

_asm cli

//----------------

_asm

{

xor eax,eax

//-------------save rigister

push ebx

push ecx

 

_emit 0x0f ; cpuid - serialise the processor

_emit 0xa2

 

//------------

_emit 0x0f ; rdtsc

_emit 0x31

 

mov ts.HighPart,edx

mov ts.LowPart,eax

 

pop ecx

pop ebx

}

//-----------------

if (!isNTOS())

_asm sti

//---------

pTime->QuadPart=ts.QuadPart;

}

else

pTime->QuadPart=0;

}

 

// maxDetermainTime:最大测定时间,单位毫秒,在首次调用该函数时,

// 将花费maxDetermineTime的时间来测定CPU频率,以后的调用将直接返加静态变量的值

double GetCPUFrequency(DWORD maxDetermineTime )

{

static double CPU_freq;

LARGE_INTEGER period,t1,t2;

register LARGE_INTEGER goal,current;

 

if (CPU_freq>1000) //this value have been initilization

return CPU_freq;

 

if (!QueryPerformanceFrequency(&period) || !checkRDSTC())

{

CPU_freq=-1.00;

return CPU_freq;

}

 

QueryPerformanceCounter(&goal);

goal.QuadPart += period.QuadPart * maxDetermineTime/1000;

 

getTime3( &t1); //开始计时

do //延时maxDetermineTime毫秒

{

QueryPerformanceCounter(&current);

} while(current.QuadPart<goal.QuadPart);

getTime3(&t2); //结束计时

 

CPU_freq=double((t2.QuadPart-t1.QuadPart)*1000/maxDetermineTime);

 

char buff[100];

sprintf(buff,"Estimated the processor clock frequency =%gHz/n",CPU_freq);

::MessageBox(NULL,buff,"",MB_OK);

return CPU_freq;

}

 

void test3()

{

LARGE_INTEGER t,t1,t2;

double f1,f2;

 

GetCPUFrequency(100); //花费0.1秒时间计算CPU频率

f1=getTime2();

getTime3(&t1);

Sleep(1000);

getTime3(&t2);

f2=getTime2();

 

t.QuadPart=t2.QuadPart-t1.QuadPart;

printf("It take %.8f second by getTime3/n",(double)t.QuadPart/GetCPUFrequency(100));

printf("It take %.8f second by getTime2/n",f2-f1);

}

 

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

{

test1();

test2();

test3();

return 0;

}

转自:liangbch@263.net