关于圆周率,可参阅“维基百科-圆周率”。我前天在博客园也发表了一篇随笔:“计算圆周率的C程序” 。这个C#版的计算圆周率程序就是在C程序的基础上改写的。C#版的程序必须使用C#2.0编译,算法和C程序是一样的,都是利用圆周率的反正切展式的泰勒级数来计算,但C#程序充分使用面象对象的编程方法,并且程序中有适当的注释,比C程序容易理解多了。C#程序从配置文件中读取计算所用的公式,允许自己增加计算公式。
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Diagnostics;
namespace Skyiv.Util
{
// 用反正切展式计算圆周率
// 例如: pi= + 16 * arctan(1/5) - 4 * arctan(1/239) [Machin]
sealed class ThePi
{
// 表示 (positive ? + : -) coefficient * arctan(1/denominator)
public sealed class Term
{
bool positive; // 系数是否正数
int coefficient; // 系数
int denominator; // 分母
public bool Positive { get { return positive; } }
public int Coefficient { get { return coefficient; } }
public int Denominator { get { return denominator; } }
public Term(bool positive, int coefficient, int denominator)
{
this.positive = positive;
this.coefficient = coefficient;
this.denominator = denominator;
}
public override string ToString()
{
return string.Format("{0} {1} * arctan(1/{2}) ", positive ? "+" : "-", coefficient, denominator);
}
}
const int overDigits = 3; // 额外计算的位数,以消除误差
List<Term> list = new List<Term>(); // 反正切展式
string name; // 反正切展式的发现者
public ThePi(string name)
{
this.name = name;
}
public void Add(Term term)
{
list.Add(term);
}
// 返回反正切展式,例如: pi= + 16 * arctan(1/5) - 4 * arctan(1/239) [Machin]
public override string ToString()
{
string s = "pi= ";
foreach (Term term in list) s += term.ToString();
return s + "[" + name + "]";
}
// 将圆周率精确到小数点后 digits 位的值以及所用时间输出到 tw
public void Out(TextWriter tw, int digits)
{
const int digitsPerGroup = 5;
const int groupsPerLine = 13;
const int digitsPerLine = groupsPerLine * digitsPerGroup;
tw.WriteLine(this);
TimeSpan elapsed;
int [] piValue = Compute(digits, out elapsed);
tw.Write("pi= {0}.", piValue[piValue.Length - 1]);
int position = 6;
for (int i = piValue.Length - 2; i >= overDigits; i--, position++)
{
tw.Write(piValue[i]);
if (position % digitsPerLine == 0) tw.WriteLine();
else if (position % digitsPerGroup == 0) tw.Write(" ");
}
if ((position - 1) % digitsPerLine != 0) tw.WriteLine();
tw.WriteLine("[{0}] DIGITS:{1:N0} ELAPSED:{2}", name, digits, elapsed);
}
// 计算圆周率到小数点后 digits 位, 并统计所用时间 elapsed
int [] Compute(int digits, out TimeSpan elapsed)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
int [] piValue = Compute(digits + overDigits);
Format(piValue);
stopWatch.Stop();
elapsed = stopWatch.Elapsed;
return piValue;
}
// 计算圆周率到小数点后 digits 位, 结果未格式化
int [] Compute(int digits)
{
int [] pi = new int [digits + 1]; // 圆周率的值, 反序存放,如: 951413
int [] tmp = new int [digits + 1]; // 中间计算结果,也是反序存放
foreach (Term term in list)
{
// arctan(x) = x - x^3/3 + x^5/5 - x^7/7 + x^9/9 - x^11/11 + ..
int validDigits = digits;
int divisor = term.Denominator;
bool positive = term.Positive;
Array.Clear(tmp, 0, tmp.Length);
tmp[digits] = term.Coefficient;
Divide(true, positive, true, ref validDigits, pi, tmp, divisor);
positive = !positive;
divisor *= divisor;
for (int step = 3; validDigits > 0; positive = !positive, step += 2)
{
Divide(false, true, true, ref validDigits, null, tmp, divisor);
Divide(true, positive, false, ref validDigits, pi, tmp, step);
}
}
return pi;
}
// 计算 sum += 或 -= (dividend /= 或 / divisor)
void Divide(
bool updateSum, // 是否更新sum
bool positive, // 系数是否正数
bool updateDividend, // 是否更新被除数
ref int digits, // 被除数的有效位数
int [] sum, // 和数
int [] dividend, // 被除数
int divisor // 除数
)
{
for (int remainder = 0, i = digits; i >= 0; i--)
{
int quotient = 10 * remainder + dividend[i];
remainder = quotient % divisor;
quotient /= divisor;
if (updateDividend) dividend[i] = quotient;
if (!updateSum) continue;
if (positive) sum[i] += quotient;
else sum[i] -= quotient;
}
if (updateDividend) while (digits > 0 && dividend[digits] == 0) digits--;
}
// 将 pi 数据组中的每个元素格式化为个位数
void Format(int [] pi)
{
for (int quotient = 0, i = 0; i < pi.Length; i++)
{
int numerator = pi[i] + quotient;
quotient = numerator / 10;
int remainder = numerator % 10;
if (remainder < 0)
{
remainder += 10;
quotient--;
}
pi[i] = remainder;
}
}
}
// 用一组反正切展式表示圆周率
sealed class Pi
{
List<ThePi> list = new List<ThePi>();
/* 从 ini 文件中读入初始配置,其格式如下:
# eg. pi = 16 * arctan(1/5) - 4 * arctan(1/239) [Machin]
Stormer + 176 57 + 28 239 - 48 682 + 96 12943
Stomer + 24 8 + 8 57 + 4 239
Gauss + 48 18 + 32 57 - 20 239
Machin + 16 5 - 4 239
*/
public Pi(string iniFileName)
{
using (StreamReader sr = new StreamReader(iniFileName))
{
Regex regex = new Regex(@"\s+");
for (;;)
{
string s = sr.ReadLine();
if (s == null) break;
if (s.Length == 0 || s[0] == '#') continue;
string [] ss = regex.Split(s);
if (ss.Length % 3 != 1) throw new ApplicationException("ini文件栏目数不正确");
ThePi thePi = new ThePi(ss[0]);
for (int i = 1; i < ss.Length; i += 3)
thePi.Add(new ThePi.Term(ss[i][0] == '+', int.Parse(ss[i+1]), int.Parse(ss[i+2])));
list.Add(thePi);
}
}
}
// 输出全部的反正切展式
public void Out(TextWriter tw)
{
int i = 0;
foreach (ThePi thePi in list) tw.WriteLine("{0,2}: {1}", ++i, thePi);
}
// 使用 formula 展式计算圆周率精确到小数点后 digits 位,并将其值以及所用时间输出到 tw
public void Out(TextWriter tw, int formula, int digits)
{
list[formula].Out(tw, digits);
}
}
sealed class TheMain
{
static void Main(string [] args)
{
TextWriter tw = Console.Out;
try
{
Pi pi = new Pi("pi.ini");
if (args.Length < 1)
{
pi.Out(tw);
tw.WriteLine("Usage: pi formula [ digits ]");
return;
}
int formula = Convert.ToInt32(args[0]);
int digits = 1000;
if (args.Length > 1) digits = Convert.ToInt32(args[1]);
if (digits < 0) digits = 0;
pi.Out(tw, formula - 1, digits);
}
catch (Exception ex)
{
tw.WriteLine(ex.Message);
}
}
}
}
该程序需要的配置文件(pi.ini)如下:
----------------------------------------------------------
# eg. pi = 16 * arctan(1/5) - 4 * arctan(1/239) [Machin]
Stormer + 176 57 + 28 239 - 48 682 + 96 12943
Stomer + 24 8 + 8 57 + 4 239
Gauss + 48 18 + 32 57 - 20 239
Machin + 16 5 - 4 239
----------------------------------------------------------
运行结果:
C> pi
1: pi= + 176 * arctan(1/57) + 28 * arctan(1/239) - 48 * arctan(1/682) + 96 * arctan(1/12943) [Stormer]
2: pi= + 24 * arctan(1/8) + 8 * arctan(1/57) + 4 * arctan(1/239) [Stomer]
3: pi= + 48 * arctan(1/18) + 32 * arctan(1/57) - 20 * arctan(1/239) [Gauss]
4: pi= + 16 * arctan(1/5) - 4 * arctan(1/239) [Machin]
Usage: pi formula [ digits ]
C> pi 4
pi= + 16 * arctan(1/5) - 4 * arctan(1/239) [Machin]
pi= 3.14159 26535 89793 23846 26433 83279 50288 41971 69399 37510 58209 74944
59230 78164 06286 20899 86280 34825 34211 70679 82148 08651 32823 06647 09384
46095 50582 23172 53594 08128 48111 74502 84102 70193 85211 05559 64462 29489
54930 38196 44288 10975 66593 34461 28475 64823 37867 83165 27120 19091 45648
56692 34603 48610 45432 66482 13393 60726 02491 41273 72458 70066 06315 58817
48815 20920 96282 92540 91715 36436 78925 90360 01133 05305 48820 46652 13841
46951 94151 16094 33057 27036 57595 91953 09218 61173 81932 61179 31051 18548
07446 23799 62749 56735 18857 52724 89122 79381 83011 94912 98336 73362 44065
66430 86021 39494 63952 24737 19070 21798 60943 70277 05392 17176 29317 67523
84674 81846 76694 05132 00056 81271 45263 56082 77857 71342 75778 96091 73637
17872 14684 40901 22495 34301 46549 58537 10507 92279 68925 89235 42019 95611
21290 21960 86403 44181 59813 62977 47713 09960 51870 72113 49999 99837 29780
49951 05973 17328 16096 31859 50244 59455 34690 83026 42522 30825 33446 85035
26193 11881 71010 00313 78387 52886 58753 32083 81420 61717 76691 47303 59825
34904 28755 46873 11595 62863 88235 37875 93751 95778 18577 80532 17122 68066
13001 92787 66111 95909 21642 01989
[Machin] DIGITS:1,000 ELAPSED:00:00:00.0342141
C> pi 1 10000
pi= + 176 * arctan(1/57) + 28 * arctan(1/239) - 48 * arctan(1/682) + 96 * arctan(1/12943) [Stormer]
pi= 3.14159 26535 89793 23846 26433 83279 50288 41971 69399 37510 58209 74944
...
24988 72758 46101 26483 69998 92256 95968 81592 05600 10165 52563 75678
[Stormer] DIGITS:10,000 ELAPSED:00:00:03.7817852
运行环境是 Pentium 4 2.8GHz PC机。