如何使用 C# 计算 PI 的值?
我在想这将是通过一个递归函数,如果是这样,它会是什么样子,是否有任何数学方程式可以支持它?
我对性能并不太挑剔,主要是从学习的角度来看如何去做。
如果你想递归:
PI = 2 * (1 + 1/3 * (1 + 2/5 * (1 + 3/7 * (...))))
经过一些重写后,这将变为:
PI = 2 * F(1);
与 F(i):
double F (int i) {
return 1 + i / (2.0 * i + 1) * F(i + 1);
}
艾萨克牛顿(你可能以前听说过他;))想出了这个把戏。请注意,为了简单起见,我省略了结束条件。在现实生活中,你有点需要一个。
如何使用:
double pi = Math.PI;
如果您想要比这更好的精度,您将需要使用算法系统和 Decimal 类型。
如果你仔细看看这个非常好的指南:
并行编程模式:使用 .NET Framework 4 理解和应用并行模式
你会在第 70 页找到这个可爱的实现(我这边有一些小的改动):
static decimal ParallelPartitionerPi(int steps)
{
decimal sum = 0.0;
decimal step = 1.0 / (decimal)steps;
object obj = new object();
Parallel.ForEach(
Partitioner.Create(0, steps),
() => 0.0,
(range, state, partial) =>
{
for (int i = range.Item1; i < range.Item2; i++)
{
decimal x = (i - 0.5) * step;
partial += 4.0 / (1.0 + x * x);
}
return partial;
},
partial => { lock (obj) sum += partial; });
return step * sum;
}
有几个非常非常古老的技巧我很惊讶在这里没有看到。
atan(1) == PI/4,因此当存在可信赖的反正切函数时,旧栗子是 4*atan(1)。
一个非常可爱的固定比率估计值使旧的西方 22/7 看起来像泥土是 355/113,它可以精确到小数点后几位(我认为至少三到四位)。在某些情况下,这对于整数运算来说已经足够好了:乘以 355 然后除以 113。
355/113 也很容易记住(无论如何对某些人来说):数一、一、三、三、五、五,并记住你正在命名分母和分子中的数字(如果你忘记了哪个三连音最重要的是,微秒的想法通常会理顺它)。
请注意,22/7 给您:3.14285714,这是错误的千分之一。
355/113 给你 3.14159292 直到百万分之十才没有错。
会计。到我的盒子上的/usr/include/math.h,M_PI 是#define'd 为:3.14159265358979323846,这可能是好的。
你从估计 PI 中得到的教训是,有很多方法可以做到这一点,没有一种方法是完美的,你必须根据预期用途对它们进行分类。
355/113 是一个古老的中国估计,我相信它比 22/7 早了很多年。这是我本科时一位物理学教授教我的。
不同算法的良好概述:
我不确定第一个链接中 Gauss-Legendre-Salamin 算法所声称的复杂性(我会说 O(N log^2(N) log(log(N))))。
不过,我鼓励您尝试一下,收敛速度非常快。
另外,我不太确定为什么要尝试将一个非常简单的过程算法转换为递归算法?
请注意,如果您对性能感兴趣,那么以有限的精度工作(通常需要'double'、'float'、...输出)并没有真正意义,因为在这种情况下,显而易见的答案就是硬编码值。
什么是 PI?圆的周长除以其直径。
在计算机图形学中,您可以从初始点 x,y 以 0,0 为中心绘制/绘制一个圆,可以使用一个简单的公式找到下一个点 x',y':x' = x + y / h: y' = y - x' / h
h 通常是 2 的幂,因此可以通过移位轻松完成除法(或从双精度数的指数中减去)。h 也想成为你的圆的半径 r。一个简单的起点是 x = r,y = 0,然后计算 c 的步数,直到 x <= 0 以绘制四分之一圆。PI 为 4 * c / r 或 PI 为 4 * c / h
递归到任何深度对于商业程序通常是不切实际的,但尾递归允许算法递归地表达,同时实现为循环。递归搜索算法有时可以使用队列而不是进程的堆栈来实现,搜索必须从死端回溯并采取另一条路径 - 这些回溯点可以放入队列中,多个进程可以取消排队并尝试其他路径。
计算如下:
x = 1 - 1/3 + 1/5 - 1/7 + 1/9 (... etc as far as possible.)
PI = x * 4
你有 Pi !!!
这是我所知道的最简单的方法。
PI 的值慢慢收敛到 Pi 的实际值(3.141592165……)。如果迭代次数越多越好。
这是一个不错的方法(来自pi 上的主要维基百科条目);它的收敛速度比上面讨论的简单公式要快得多,并且如果您打算将递归作为学习练习,它非常适合递归解决方案。(假设您追求学习经验,我不会给出任何实际代码。)
基本公式与上述相同,但这种方法对部分和进行平均以加速收敛。
定义一个双参数函数 pie(h, w),这样:
pie(0,1) = 4/1
pie(0,2) = 4/1 - 4/3
pie(0,3) = 4/1 - 4/3 + 4/5
pie(0,4) = 4/1 - 4/3 + 4/5 - 4/7
... and so on
因此,您探索递归的第一个机会是将“水平”计算编码为“宽度”参数增加(对于“高度”为零)。
然后用这个公式添加第二个维度:
pie(h, w) = (pie(h-1,w) + pie(h-1,w+1)) / 2
当然,它仅用于 h 大于零的值。
这个算法的好处是你可以很容易地用一个电子表格来模拟它,当你探索由逐渐变大的参数产生的结果时检查你的代码。当您计算 pie(10,10) 时,您将获得 pi 的近似值,该值足以满足大多数工程目的。
Enumerable.Range(0, 100000000).Aggregate(0d, (tot, next) => tot += Math.Pow(-1d, next)/(2*next + 1)*4)
using System;
namespace Strings
{
class Program
{
static void Main(string[] args)
{
/* decimal pie = 1;
decimal e = -1;
*/
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start(); //added this nice stopwatch start routine
//leibniz formula in C# - code written completely by Todd Mandell 2014
/*
for (decimal f = (e += 2); f < 1000001; f++)
{
e += 2;
pie -= 1 / e;
e += 2;
pie += 1 / e;
Console.WriteLine(pie * 4);
}
decimal finalDisplayString = (pie * 4);
Console.WriteLine("pie = {0}", finalDisplayString);
Console.WriteLine("Accuracy resulting from approximately {0} steps", e/4);
*/
// Nilakantha formula - code written completely by Todd Mandell 2014
// π = 3 + 4/(2*3*4) - 4/(4*5*6) + 4/(6*7*8) - 4/(8*9*10) + 4/(10*11*12) - (4/(12*13*14) etc
decimal pie = 0;
decimal a = 2;
decimal b = 3;
decimal c = 4;
decimal e = 1;
for (decimal f = (e += 1); f < 100000; f++)
// Increase f where "f < 100000" to increase number of steps
{
pie += 4 / (a * b * c);
a += 2;
b += 2;
c += 2;
pie -= 4 / (a * b * c);
a += 2;
b += 2;
c += 2;
e += 1;
}
decimal finalDisplayString = (pie + 3);
Console.WriteLine("pie = {0}", finalDisplayString);
Console.WriteLine("Accuracy resulting from {0} steps", e);
stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;
Console.WriteLine("Calc Time {0}", ts);
Console.ReadLine();
}
}
}
public static string PiNumberFinder(int digitNumber)
{
string piNumber = "3,";
int dividedBy = 11080585;
int divisor = 78256779;
int result;
for (int i = 0; i < digitNumber; i++)
{
if (dividedBy < divisor)
dividedBy *= 10;
result = dividedBy / divisor;
string resultString = result.ToString();
piNumber += resultString;
dividedBy = dividedBy - divisor * result;
}
return piNumber;
}
在任何生产场景中,我都会强迫您查找该值,到所需的小数位数,并将其存储为您的类可以访问的“常量”。
(除非您正在编写科学的“Pi”特定软件......)
关于...
...如何从学习的角度去做。
您是否正在尝试学习编程科学方法?还是生产生产软件?我希望社区认为这是一个有效的问题,而不是挑剔。
无论哪种情况,我认为编写自己的 Pi 是一个已解决的问题。Dmitry 已经显示了“Math.PI”常数。攻击同一空间的另一个问题!寻找通用的牛顿近似值或一些巧妙的东西。
@Thomas Kammeyer:
请注意,Atan(1.0) 经常是硬编码的,所以 4*Atan(1.0) 如果您正在调用库 Atan 函数,则并不是真正的“算法”(很多已经建议通过将 Atan(x) 替换为一个系列(或无限产品),然后在 x=1 处评估它。
此外,在极少数情况下,您需要比几十位更精确的 pi(这可以很容易地硬编码!)。我从事过数学应用程序,为了计算一些(相当复杂的)数学对象(它们是具有整数系数的多项式),我必须对实数和复数(包括计算 pi)进行算术运算,精度高达几百万比特......但这在“现实生活”中并不常见:)
您可以查看以下示例代码。
以下链接显示了如何根据其定义为积分计算 pi 常数,可以写为求和的限制,非常有趣: https ://sites.google.com/site/rcorcs/posts/calculatingthepiconstant 文件“Pi 作为一个整体”解释了本文中使用的这种方法。
我喜欢这篇论文,它解释了如何根据反正切的泰勒级数展开来计算 π。
本文从一个简单的假设开始,即
Atan(1) = π/4 弧度
Atan(x) 可以用泰勒级数迭代估计
atan(x) = x - x^3/3 + x^5/5 - x^7/7 + x^9/9...
该论文指出了为什么这不是特别有效,并继续对该技术进行了一些逻辑上的改进。他们还提供了一个将 π 计算到几千位的示例程序,并附有源代码,包括所需的无限精度数学例程。
首先,请注意 C# 可以使用 .NET 框架的 Math.PI 字段:
https://msdn.microsoft.com/en-us/library/system.math.pi(v=vs.110).aspx
这里的一个不错的特性是它是一个全精度双精度,您可以使用它,也可以与计算结果进行比较。该 URL 上的选项卡在 C++、F# 和 Visual Basic 中具有类似的常量。
要计算更多位置,您可以编写自己的扩展精度代码。一种快速编码且相当快速且易于编程的方法是:
Pi = 4 * [4 * arctan (1/5) - arctan (1/239)]
这个公式和许多其他公式,包括一些以惊人的速度收敛的公式,例如每项 50 位,都在 Wolfram:
PI (π)可以通过使用无穷级数来计算。这里有两个例子:
格雷戈里-莱布尼茨系列:
π/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ...
C#方法:
public static decimal GregoryLeibnizGetPI(int n)
{
decimal sum = 0;
decimal temp = 0;
for (int i = 0; i < n; i++)
{
temp = 4m / (1 + 2 * i);
sum += i % 2 == 0 ? temp : -temp;
}
return sum;
}
尼拉坎塔系列:
π = 3 + 4 / (2x3x4) - 4 / (4x5x6) + 4 / (6x7x8) - 4 / (8x9x10) + ...
C#方法:
public static decimal NilakanthaGetPI(int n)
{
decimal sum = 0;
decimal temp = 0;
decimal a = 2, b = 3, c = 4;
for (int i = 0; i < n; i++)
{
temp = 4 / (a * b * c);
sum += i % 2 == 0 ? temp : -temp;
a += 2; b += 2; c += 2;
}
return 3 + sum;
}
两个函数的输入参数n
表示迭代次数。
Nilakantha 系列与 Gregory-Leibniz 系列相比收敛更快。可以使用以下代码测试这些方法:
static void Main(string[] args)
{
const decimal pi = 3.1415926535897932384626433832m;
Console.WriteLine($"PI = {pi}");
//Nilakantha Series
int iterationsN = 100;
decimal nilakanthaPI = NilakanthaGetPI(iterationsN);
decimal CalcErrorNilakantha = pi - nilakanthaPI;
Console.WriteLine($"\nNilakantha Series -> PI = {nilakanthaPI}");
Console.WriteLine($"Calculation error = {CalcErrorNilakantha}");
int numDecNilakantha = pi.ToString().Zip(nilakanthaPI.ToString(), (x, y) => x == y).TakeWhile(x => x).Count() - 2;
Console.WriteLine($"Number of correct decimals = {numDecNilakantha}");
Console.WriteLine($"Number of iterations = {iterationsN}");
//Gregory-Leibniz Series
int iterationsGL = 1000000;
decimal GregoryLeibnizPI = GregoryLeibnizGetPI(iterationsGL);
decimal CalcErrorGregoryLeibniz = pi - GregoryLeibnizPI;
Console.WriteLine($"\nGregory-Leibniz Series -> PI = {GregoryLeibnizPI}");
Console.WriteLine($"Calculation error = {CalcErrorGregoryLeibniz}");
int numDecGregoryLeibniz = pi.ToString().Zip(GregoryLeibnizPI.ToString(), (x, y) => x == y).TakeWhile(x => x).Count() - 2;
Console.WriteLine($"Number of correct decimals = {numDecGregoryLeibniz}");
Console.WriteLine($"Number of iterations = {iterationsGL}");
Console.ReadKey();
}
以下输出显示 Nilakantha 系列在 100 次迭代中返回 PI 的六个正确小数,而 Gregory-Leibniz 系列在 100 万次迭代中返回 PI 的五个正确小数:
我的代码可以测试>>这里
这是一个很好的方法:计算一系列 1/x^2 的 x,从 1 到您想要的任何值 - 数字越大 - 饼图结果越好。将结果乘以 6 并乘以 sqrt()。这是 c# 中的代码(仅主要):
static void Main(string[] args)
{
double counter = 0;
for (double i = 1; i < 1000000; i++)
{
counter = counter + (1 / (Math.Pow(i, 2)));
}
counter = counter * 6;
counter = Math.Sqrt(counter);
Console.WriteLine(counter);
}
public double PI = 22.0 / 7.0;