前几天,我发表了一篇随笔:“BigArithmetic - 提供任意精度的算术运算的静态类”。现在,让我们使用 BigArithmetic 类来计算圆周率。
我们需要一个计算 π 的分析算法。有用的算法是二次收敛的,即每一次迭代使有效位数增加一倍。计算 π 的二次收敛算法是基于 ACM 法(算术几何平均法)。首先设置初始值为:
然后,当 i = 0, 1, ... 重复迭代:
直到足够的精度。
下面就是源程序代码:
1
using
System;
2
3 namespace Skyiv.Numeric
4 {
5 /// <summary>
6 /// 圆周率
7 /// </summary>
8 static class Pi
9 {
10 // = pp.780-781, mppi, 20.6 任意精度的运算
11 /// <summary>
12 /// 计算圆周率到小数点后 digits 位数字。
13 /// 计算结果保存在返回的字节数组中,每个字节存放两个十进制数字,字节数组的第一个元素是“03”。
14 /// 字节数组的长度可能大于 (digits + 1) / 2 + 1,只保证小数点后前 digits 个十进制数字是准确的。
15 /// </summary>
16 /// <param name="digits"> 小数点后的十进制数字个数 </param>
17 /// <returns> 存放圆周率的字节数组 </returns>
18 public static byte [] Compute( int digits)
19 {
20 if (digits < 0 ) throw new ArgumentOutOfRangeException( " digits " , " can't less than zero " );
21 int n = Math.Max( 5 , (digits + 1 ) / 2 + 2 );
22 byte [] pi = new byte [n + 1 ];
23 byte [] x = new byte [n + 1 ], y = new byte [n << 1 ];
24 byte [] sx = new byte [n], sxi = new byte [n];
25 byte [] t = new byte [n << 1 ], s = new byte [ 3 * n];
26 t[ 0 ] = 2 ; // t = 2
27 BigArithmetic.Sqrt(x, x, n, t, n); // x = sqrt(2)
28 BigArithmetic.Add(pi, t, x, n); // pi = 2 + sqrt(2)
29 Array.Copy(pi, 1 , pi, 0 , n);
30 BigArithmetic.Sqrt(sx, sxi, n, x, n); // sx = sqrt(x)
31 Array.Copy(sx, y, n); // y = sqrt(x)
32 for (; ; )
33 {
34 BigArithmetic.Add(x, sx, sxi, n); // x = sqrt(x) + 1/sqrt(x)
35 Array.Copy(x, 1 , x, 0 , n);
36 BigArithmetic.Divide(x, x, n, 2 ); // x = x / 2
37 BigArithmetic.Sqrt(sx, sxi, n, x, n); // sx = sqrt(x), sxi = 1/sqrt(x)
38 BigArithmetic.Multiply(t, y, n, sx, n); // t = y * sx
39 Array.Copy(t, 1 , t, 0 , n);
40 BigArithmetic.Add(t, t, sxi, n); // t = y * sx + sxi
41 x[ 0 ] ++ ;
42 y[ 0 ] ++ ;
43 BigArithmetic.Inverse(s, n, y, n); // s = 1 / (y + 1)
44 Array.Copy(t, 1 , t, 0 , n);
45 BigArithmetic.Multiply(y, t, n, s, n); // y = t / (y + 1)
46 Array.Copy(y, 1 , y, 0 , n);
47 BigArithmetic.Multiply(t, x, n, s, n); // t = (x + 1) / (y + 1)
48 int mm = t[ 1 ] - 1 ; // 若 t == 1 则收敛
49 int j = t[n] - mm;
50 if (j > 1 || j < - 1 )
51 {
52 for (j = 2 ; j < n; j ++ )
53 {
54 if (t[j] != mm)
55 {
56 Array.Copy(t, 1 , t, 0 , n);
57 BigArithmetic.Multiply(s, pi, n, t, n); // s = t * pi
58 Array.Copy(s, 1 , pi, 0 , n); // pi = t * pi
59 break ;
60 }
61 }
62 if (j < n) continue ;
63 }
64 break ;
65 }
66 return pi;
67 }
68 }
69 }
2
3 namespace Skyiv.Numeric
4 {
5 /// <summary>
6 /// 圆周率
7 /// </summary>
8 static class Pi
9 {
10 // = pp.780-781, mppi, 20.6 任意精度的运算
11 /// <summary>
12 /// 计算圆周率到小数点后 digits 位数字。
13 /// 计算结果保存在返回的字节数组中,每个字节存放两个十进制数字,字节数组的第一个元素是“03”。
14 /// 字节数组的长度可能大于 (digits + 1) / 2 + 1,只保证小数点后前 digits 个十进制数字是准确的。
15 /// </summary>
16 /// <param name="digits"> 小数点后的十进制数字个数 </param>
17 /// <returns> 存放圆周率的字节数组 </returns>
18 public static byte [] Compute( int digits)
19 {
20 if (digits < 0 ) throw new ArgumentOutOfRangeException( " digits " , " can't less than zero " );
21 int n = Math.Max( 5 , (digits + 1 ) / 2 + 2 );
22 byte [] pi = new byte [n + 1 ];
23 byte [] x = new byte [n + 1 ], y = new byte [n << 1 ];
24 byte [] sx = new byte [n], sxi = new byte [n];
25 byte [] t = new byte [n << 1 ], s = new byte [ 3 * n];
26 t[ 0 ] = 2 ; // t = 2
27 BigArithmetic.Sqrt(x, x, n, t, n); // x = sqrt(2)
28 BigArithmetic.Add(pi, t, x, n); // pi = 2 + sqrt(2)
29 Array.Copy(pi, 1 , pi, 0 , n);
30 BigArithmetic.Sqrt(sx, sxi, n, x, n); // sx = sqrt(x)
31 Array.Copy(sx, y, n); // y = sqrt(x)
32 for (; ; )
33 {
34 BigArithmetic.Add(x, sx, sxi, n); // x = sqrt(x) + 1/sqrt(x)
35 Array.Copy(x, 1 , x, 0 , n);
36 BigArithmetic.Divide(x, x, n, 2 ); // x = x / 2
37 BigArithmetic.Sqrt(sx, sxi, n, x, n); // sx = sqrt(x), sxi = 1/sqrt(x)
38 BigArithmetic.Multiply(t, y, n, sx, n); // t = y * sx
39 Array.Copy(t, 1 , t, 0 , n);
40 BigArithmetic.Add(t, t, sxi, n); // t = y * sx + sxi
41 x[ 0 ] ++ ;
42 y[ 0 ] ++ ;
43 BigArithmetic.Inverse(s, n, y, n); // s = 1 / (y + 1)
44 Array.Copy(t, 1 , t, 0 , n);
45 BigArithmetic.Multiply(y, t, n, s, n); // y = t / (y + 1)
46 Array.Copy(y, 1 , y, 0 , n);
47 BigArithmetic.Multiply(t, x, n, s, n); // t = (x + 1) / (y + 1)
48 int mm = t[ 1 ] - 1 ; // 若 t == 1 则收敛
49 int j = t[n] - mm;
50 if (j > 1 || j < - 1 )
51 {
52 for (j = 2 ; j < n; j ++ )
53 {
54 if (t[j] != mm)
55 {
56 Array.Copy(t, 1 , t, 0 , n);
57 BigArithmetic.Multiply(s, pi, n, t, n); // s = t * pi
58 Array.Copy(s, 1 , pi, 0 , n); // pi = t * pi
59 break ;
60 }
61 }
62 if (j < n) continue ;
63 }
64 break ;
65 }
66 return pi;
67 }
68 }
69 }
这个程序很简单,就是按照前面给出的公式进行计算而已。
下面是测试程序:
1
using
System;
2 using System.Text;
3 using System.Diagnostics;
4 using Skyiv.Numeric;
5
6 namespace Skyiv
7 {
8 sealed class Test
9 {
10 static void Main( string [] args)
11 {
12 var stopWatch = Stopwatch.StartNew();
13 int digits = (args.Length > 0 ) ? int .Parse(args[ 0 ]) : 10000 ;
14 var bs = Pi.Compute(digits);
15 var sb = new StringBuilder();
16 sb.AppendFormat( " Pi = {0}.{1} " , bs[ 0 ], Environment.NewLine);
17 for ( int i = 1 ; i <= (digits + 1 ) / 2 ; i ++ )
18 {
19 sb.Append(bs[i].ToString( " D2 " ));
20 if (i % 25 == 0 ) sb.AppendLine();
21 else if (i % 5 == 0 ) sb.Append( ' ' );
22 }
23 stopWatch.Stop();
24 if ((digits + 1 ) / 2 % 25 != 0 ) sb.AppendLine();
25 sb.AppendFormat( " DIGITS:{0:N0} ELAPSED:{1} " , digits, stopWatch.Elapsed);
26 Console.Write(sb);
27 }
28 }
29 }
2 using System.Text;
3 using System.Diagnostics;
4 using Skyiv.Numeric;
5
6 namespace Skyiv
7 {
8 sealed class Test
9 {
10 static void Main( string [] args)
11 {
12 var stopWatch = Stopwatch.StartNew();
13 int digits = (args.Length > 0 ) ? int .Parse(args[ 0 ]) : 10000 ;
14 var bs = Pi.Compute(digits);
15 var sb = new StringBuilder();
16 sb.AppendFormat( " Pi = {0}.{1} " , bs[ 0 ], Environment.NewLine);
17 for ( int i = 1 ; i <= (digits + 1 ) / 2 ; i ++ )
18 {
19 sb.Append(bs[i].ToString( " D2 " ));
20 if (i % 25 == 0 ) sb.AppendLine();
21 else if (i % 5 == 0 ) sb.Append( ' ' );
22 }
23 stopWatch.Stop();
24 if ((digits + 1 ) / 2 % 25 != 0 ) sb.AppendLine();
25 sb.AppendFormat( " DIGITS:{0:N0} ELAPSED:{1} " , digits, stopWatch.Elapsed);
26 Console.Write(sb);
27 }
28 }
29 }
运行结果如下:
Pi = 3.1415926535 8979323846 2643383279 5028841971 6939937510
5820974944 5923078164 0628620899 8628034825 3421170679
...
4610126483 6999892256 9596881592 0560010165 5256375678
DIGITS:10,000 ELAPSED:00:00:04.0998524
这个结果可以和我在2005年9月30日写的“计算圆周率的C#程序”这篇随笔中的结果相对照。