前几天,我发表了一篇随笔:“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#程序”这篇随笔中的结果相对照。