对于求π程序的深入解读

对于求π程序的深入解读

此文所用到的方法原理可以用到由级数或者泰勒公式产生的无理数的运算之中

源代码展示

#include<stdio.h>
#include<math.h>
#include<malloc.h>
int main()
{
    
    
    double s;
    int b,x,n,c,i,j,d,l;
    printf("请输入精确位数:");
    scanf("%d",&x);
   //x应为8位数及以下,理论上小数点后一亿位时,内存分配约3个多G;n亦在有效范围内

    short int *a=(short int*)malloc(sizeof(short int)*(6+x));
     for(s=0,n=1;;n++)//累加确定项数.
     {
    
    
       s=s+log10((2*n+1)/n);
       if(s>x+1)
        break;
     }
     for(i=0;i<=x+5;i++)//初始化前x+5个元素为0;
       a[i]=0;
     for(c=1,j=n;j>=1;j--)//按公式分布计算。
     {
    
    
       d=2*j+1;
       for(i=0;i<=x+4;i++)//各位实施除2j+1.
       {
    
    
        a[i]=c/d;         //
        c=(c%d)*10+a[i+1];//模仿手工除法求商
       }
       a[x+5]=c/d;
       for(b=0,i=x+5;i>=0;i--)//各位实施乘j
       {
    
    
        a[i]=a[i]*j+b;//
        b=a[i]/10;    //
        a[i]=a[i]%10;//模仿手工乘法进位
       }
       a[0]=a[0]+1;//整数加1,即公式中的1+n/(2n+1)中的1;
       c=a[0];//下次循环中的起始c是上次计算得到的1+n/(2n+1)的整数部分
     }
     for(b=0,i=x+5;i>=0;i--)//按公式各位乘2
     {
    
    
       a[i]=a[i]*2+b;
       b=a[i]/10;
       a[i]=a[i]%10;
     }
     printf("\n%d----PI=%d.",x,a[0]);
     for(l=10,i=1;i<=x;i++)
     {
    
    
       printf("%d",a[i]);
       l++;
       if(l%10==0)
        putchar(' ');
       if(l%50==0)
        putchar('\n');
     }
     putchar('\n');
     free(a);
return 0;
//这个程序将π 的每一位数放进一个数组元素中,a[0]是整数位,a[ i ]分别对应小数点后 i 位

逐行分析

  • 6,7,8行不用说

定义程序之中要使用的变量,具体用途在讲解过程中你会一一明白,跟着我的思路走就行。
第9行要求输入所需要的精确的位数,以 x 来存储。在此次讲解中我们以 x = 5,即要求 π 小数点后5位来举例。

  • 第12行:
int *a=(int*)malloc(sizeof(int)*(6+x));

动态分配x+6=11个元素的内存空间来。为什么要11个元素呢,不是只要求了小数点后 5 位加上整数部分共 6 个数吗?
这是为了保证求解过程中的精确度,不能因为数字位数不足而导致在计算过程中将误差一步一步放大。小数部分只有5位,我用6个元素来存储π的每一位数,剩下5个元素就是我来确保其精确度的部分,可以看到,在第45行输出整数部分 和第 46 行循环输出时 ,我只输出了小数部分的 a[ 1 ] 到 a[ x ]共计 x 个数,即π 的后 x 位,剩余部分被丢弃。

  • 第13 ~18行的循环
 for(s=0,n=1;;n++)//累加确定项数.
{
    
    
    s=s+log10((2*n+1)/n);
    if(s>x+1)
    break;
}

为什么要使用这个循环呢?我们从公式着手,我将这个公式做一次简化:

我要保证当 n 等于某一个值的时候,它对π的影响可以忽略。本例中,要求π的后5位,也就是说,我要将n的
影响控制在小数点后6位上,即
a [ n ] < 0.000001 = 1 0 − 6 = 1 0 − ( x + 1 ) a[n] < 0.000001=10^{-6}=10^{-(x+1)} a[n]<0.000001=106=10(x+1)

程序中 l o g 10 ( ) log_{10}() log10() 即就是 l g ( ) lg() lg() 计算出来后我们得到了计算公式中的 n,公式是无穷级数,当然不能算无穷次。
由这一步知,计算公式中的前 n 项和即可满足精确度的要求。

  • 第19~20行:

将数组元素全部初始化为0。

for(i=0;i<=x+5;i++)//初始化前x+5个元素为0;
    a[i]=0;
  • 第21~38行:
    这是这个程序的核心,我们一步一步来
for(c=1,j=n;j>=1;j--)//按公式分布计算。
{
    
    
    d=2*j+1;
    for(i=0;i<=x+4;i++)//各位实施除2j+1.
    {
    
    
        a[i]=c/d;         //
        c=(c%d)*10+a[i+1];//模仿手工除法求商
    }
    a[x+5]=c/d;
    for(b=0,i=x+5;i>=0;i--)//各位实施乘j
    {
    
    
        a[i]=a[i]*j+b;//
        b=a[i]/10;    //
        a[i]=a[i]%10;//模仿手工乘法进位
    }
    a[0]=a[0]+1;//整数加1,即公式中的1+n/(2n+1)中的1;
    c=a[0];//下次循环中的起始c是上次计算得到的1+n/(2n+1)的整数部分
}

模拟运行一次,第一次大循环 c =1 ,j=n
到内层:d = 2j +1 = 2n +1 。
接下来内层的第一个for循环:

 for(i=0;i<=x+4;i++)//各位实施除2j+1.
{
    
    
    a[i]=c/d;         //
    c=(c%d)*10+a[i+1];//模仿手工除法求商
}

这里涉及到了高精度除法,我在纸上讲一下这个循环的作用:

循环将每一次求出来的商的一位放进数组中。c用来暂存每一次的余数,至于为何会出现 +a[ i+1 ],
那是涉及到下一次大循环的,稍后通过一个表格来演示。先看内部这个做除法的小循环的运行过程:

  • 第一次:a [ 0 ] = 1 / 41 = 0 ;c=1%41*10+0=10;
  • 第二次:a [ 1 ] = 10/41 = 0; c = 10%41*10+0 = 100;
  • 第三次:a [ 2 ] = 100/41=2; c = 100%41 *10+0=180;
  • 第四次:a [ 3 ] = 180/41=4; c = 180%41 *10+0 =160;
    -…
  • 第x+5次即第10次:a [ 9 ] = 3;

为什么这个循环只执行x+5次呢,不是要x+6个元素吗。可以看到,循环内有一个对 c 的修改,当我们将
所需要的最后一位商求出来后,就不在需要那个余数了,所以求第x+6位商即 a [ x+5 ] =a [ 10 ]时,不对c做修改,循环外单独计算。
此时数组就保存了 1 / 41 的精确到后 10位的值: 1 / 41 = 0.0243902439 1/41 =0.0243902439 1/41=0.0243902439

Alt

  • 接下来内层的第二个for循环:
for(b=0,i=x+5;i>=0;i--)//各位实施乘j
{
    
    
    a[i]=a[i]*j+b;//
    b=a[i]/10;    //
    a[i]=a[i]%10;//模仿手工乘法进位
}

这个循环模拟高精度乘法运算,b保留每次的进位。刚刚计算了1/41,公式中是 n/(2n+1) 即就是 20 /41,
所以要给刚刚算出来的 1/41 乘上 20 ;如何将乘法运算做到数组中去呢?看下图:

如此,得到20/41即 20*(1/41)= 0.4878048780 的精确值;

然后,为什么要有 a [ 0 ] = a [ 0 ] + 1;
我们再看一下公式:

此时数组存储 1 + 20 / 41 = 1.4878048780 1+ 20/41 = 1.4878048780 1+20/41=1.4878048780 的精确值。

然后,为什么要有 c=a[0] 呢?
我们看到 c 将要做下次循环中计算 a[i]=c/d 的分子。为什么这样?
我们看上面的公式。我已经计算出了括号最内层的算式 1+ 20/41 并将其放在数组 a 中,不妨让
a表示此时的1+ 20/41即 1.4878048780。
那下次循环就要计算 a/39
我们试一下,1.48除以39 和 1 除以39 相比在计算机计算的时候有什么不同,
不错,他们是一样的因为取整除法的缘故。如 1.3/2=1/2=0, 5.3/2=5/2=2,
即就是小数部分对这个除法运算不起作用,因此我们不用小数部分。

转入第二次循环:j = n-1 = 39;
c 等于 1+ 20/41取整 即 1 ;
此时的 c/d 就相当于 (1+ 20/41)/ 39 即 a / 39 即 1 / 39;
再做除法。
我们回过头看一下为何在程序第27 行会出现

c=(c%d)*10+a[i+1]

因为这一次的被除数不是 1 ,而是被保存在数组中的 1+ 20/41 。
我们让 c 只保留 a的整数位是因为小数 在计算 c/d 中被丢弃,但数组中原有的小数依然是
保证精确度的关键。我们可以看一下这一次的计算过程:

除法算完再算乘法,即计算出 19/ 39*(1+ 20/41),算出来后再加 1 ;
得 1+ 19/ 39*(1+ 20/41),依次类推,一层一层地想外。最终数组就存储了

即得到 π/2 的近似精确值。

  • 程序的第39~44行:
    我们得到了 π/2,如何得到 π呢?乘以 2 不就完了。
    这几行程序的乘法原理和前面讲的那个循环内的乘法是一样的,只是将循环内的那个
    j 变成了 2 即给所得的数值乘以 2。

到这儿基本上就结束了,π的值已经在数组之中了。

  • 第45~ 55行:将数组中存储的π值输出,每50个数一行,每10个数一个空格,
  • 第46~54行便是控制输出这种格式的。
  • 第56行,将动态分配出来的数组内存释放,没啥用的语句。

猜你喜欢

转载自blog.csdn.net/qq_37957939/article/details/106809598