$\pi$ 是一个重要的随机数,在数学研究中占有重要地位。求解PI的数值值也一直是数学研究的经典问题之一。本文将主要探讨几种求解PI的概率算法的原理和实现, 并对比其效率和准确度。

Mathematica中的 $\pi$

我们发现,在Mathematica中可以使用 $\pi$ 来做符号运算,很多运算都涉及到非常高深的数学知识,使用N[Pi, n]函数也能够求得 $\pi$ 的前 $n$ 位数值解。 那么为什么Mathematica能够以如此高的精度求解 $\pi$ 的值呢?

通过查阅Mathematica的文档,得知,Mathematica求解 $\pi$ 使用的是Chudnovsky公式,因其具有很好的收敛速度而在数值计算中被广泛采用。Chudnovsky 算法的表述如下:

\[\frac{1}{\pi} = 12\sum_{k=0}^{\infty} \frac{(-1)^k(6k)! (163\cdot 3344418k + 13591409)}{(3k)!(k!)^3(640320^3)^{k+1/2}}\]

根据这个公式,可以编写如下Mathematica代码:

chud[n_] := N[1/(12*Sum[
	((-1)^k*(6 k)!*(13591409 + 545140134 k))/
	((3 k)!*(k!)^3*640320^(3 k + 3/2)),
    {k, 0, n}], 5];
]);

运行如下命令:

chud[1] // N

将会输出

3.14159

可见,Chudnovsky算法确实具有非常好的收敛性能。

二、普通蒙特卡洛方法

普通蒙特卡洛(Monte Carlo)方法求解 $\pi$ 的值,是指每次随机产生一个在 $2\times 2$ 的正方形中的点,统计这个点在该正方形的内接圆里的概率 $p$。 圆周率 $\pi$ 的估计值为 $4p$。算法原理和实现都很简单,Mathematica代码如下(此处,仅仅统计在第一象限内的随机点的分布情况):

mc[n_] := Module[
	{
		in = 0,
		total = 0
	},
	For[k=1,k<=n,k=k+1,
		x=RandomReal[];
		y=RandomReal[];
		total=total+1;
		If[x*x+y*y<=1,in=in+1]
	];
	N[in/total*4, 5]
];

重复运行1000000次随机:

Print[mc[1000000]];

得到如下的$\pi$的近似值的序列:

3.1432  3.1423  3.1399  3.1397  3.1429

可见,蒙特卡洛随机化算法的准确性还是比较高的。使用点落在圆内的概率来模拟 $\pi$ 的值也是很正确的选择。

三、随机数互质的概率

接下来采用另一种概率算法来计算 $\pi$ 的近似值。这个算法不同于之前的蒙特卡洛、蒲风投针等随机算法模型,也不同于通过迭代来计算 $\pi$ 的近似值的算法模型。

这一算法的依据是:两个随机正整数 $a, b$ 互质的概率为 $\frac{6}{\pi^2}$。这一性质的证明过程如下:

  1. 设两个数 $u,v$ 互质的概率为 $p$,则 $gcd(u,v)=d$ 当且仅当 $d u, d v$,$gcd(\frac{u}{d}, \frac{v}{d})=1$。
  2. 因此,任两个数最大公约数为 d 的概率为 $p/d/d$,即 $p/(d^2)$。
  3. 在正整数集合上有 \(p+p/4+p/9+ \dots +p/(n^2)+ \dots = 1\) 容易求得 \(p=\frac{6}{\pi^2}\)。

利用这一性质,只需要采取如下做法:

取一大整数 $N$,在 $1$ 到 $N$ 之间随机地取一对整数 $a, b$,找到它们的最大公约数 $(a,b)$,做 $n$ 次这样的实验,记录 $(a,b)=1$ 的情况次数 $m$, 计算出 $p=\frac{m}{n}$ 的值。便可以计算出 $\pi$ 的近似值。

该算法的Mathematica实现如下:

(* 此处采用本机Mathematica的Machine ID作为随机数的最大范围: *)
(*        6102-90797-51506 *)

primep[n_] := Module[
	{
        (* maximum range of random number. *)
		machineid = 61029079751506,
		is = 0,
		total = 0
	},
	For[k =1, k <= n, k = k+1,
		total = total + 1;
		x = RandomInteger[{1, machineid}];
		y = RandomInteger[{1, machineid}];
		If[GCD[x,y]==1, is = is+1]
	];
	N[Sqrt[6/(is/total)], 5]
];

重复运行:

For[i=1,i<=5,i=i+1,Print[primep[1000000]]];

得到如下的结果序列:

3.1408  3.1407  3.1408  3.1398  3.1419

总的来看,结果的分布还是相当不错的,作为一个随机概率算法,有如此的准确性已经相当不容易了。

四、对比

画图分析 $\pi$,Chudnovsky算法,普通蒙特卡洛算法,整数互质概率算法这四种求解 $\pi$ 的数值值得结果:

n = 10;
pis = Table[N[Pi, 5], {k, 1, n}];
chuds = Table[chud[k], {k, 1, n}];
mcs = Table[mc[Prime[2^k]], {k, 1, n}];
primeps = Table[primep[Prime[2^k]], {k, 1, n}];
Print[pis];
Print[chuds];
Print[mcs];
Print[primeps];
ListLinePlot[{pis, chuds, mcs, primeps},
    AxesOrigin -> {0, 0},
    PlotRange -> {0, 4}
]

如下图所示:

结果对比图

通过图像对比,不难看出Chudnovsky算法的高效和精确。在保留5位小数的情况下,Chudnovsky算法得到的结果和Mathematica自身算出来的结果已经重合,Chudnovsky算法仅仅做了一次迭代!

对比两种概率算法的结果,我们发现尽管是不同的概率模型,但由于其理论概率的保证,最终仍然都会收敛到 $\pi$ 的精确数值值。初期,由于基于整数互质概率的方法得到的结果偏离 实际情况过大,这也与随机整数的因数分布有关系,而当迭代次数增大时,这个方法得到的结果似乎要优于朴素蒙特卡洛方法的结果。这也启示我们,除了使用面积等古典概率 模型来进行数值模拟计算以外,数学上其他方面的一些理论成果也很有借鉴意义。

五、参考文献

  1. Chudnovsky algorithm
  2. WOLFRAM语言教程-关于内部实现的一些注释

六、附录:Mathematica代码

(* Chudnovsky algorithm*)
chud[n_] := N[1/(12*Sum[
    ((-1)^k*(6 k)!*(13591409 + 545140134 k))/
    ((3 k)!*(k!)^3*640320^(3 k + 3/2)),
    {k, 0, n}], 5];
]);

(* Monte Carlo *)
mc[n_] := Module[
    {
        in = 0,
        total = 0
    },
    For[k=1,k<=n,k=k+1,
        x=RandomReal[];
        y=RandomReal[];
        total=total+1;
        If[x*x+y*y<=1,in=in+1]
    ];
    N[in/total*4, 5]
];

(* Prime model *)
primep[n_] := Module[
    {
        (* maximum range of random number. *)
        machineid = 61029079751506,
        is = 0,
        total = 0
    },
    For[k =1, k <= n, k = k+1,
        total = total + 1;
        x = RandomInteger[{1, machineid}];
        y = RandomInteger[{1, machineid}];
        If[GCD[x,y]==1, is = is+1]
    ];
    N[Sqrt[6/(is/total)], 5]
];

(* plot *)
n = 10;
pis = Table[N[Pi, 5], {k, 1, n}];
chuds = Table[chud[k], {k, 1, n}];
mcs = Table[mc[Prime[2^k]], {k, 1, n}];
primeps = Table[primep[Prime[2^k]], {k, 1, n}];
Print[pis];
Print[chuds];
Print[mcs];
Print[primeps];
ListLinePlot[{pis, chuds, mcs, primeps},
    AxesOrigin -> {0, 0},
    PlotRange -> {0, 4}
]