9

我该如何在 C# 中将 PI 计算到一定的小数位?

我希望能够将一个数字传递给一个方法并取回计算到该小数位数的 PI。

public decimal CalculatePi(int places)
{
    // magic
    return pi;
}

Console.WriteLine(CalculatePi(5)); // Would print 3.14159

Console.WriteLine(CalculatePi(10)); // Would print 3.1415926535

ETC...

我不在乎程序的速度。我只是希望它尽可能简单易懂。在此先感谢您的帮助。

4

6 回答 6

17

首先,假设您想要 pi 的任意位数,并且我们不希望受限于任何各种浮点数的精度,让我们将 Pi 函数定义为字符串而不是任何数字类型。

我在搜索这项技术时发现的最酷的算法之一是Stanley Rabinowitz 和 Stan Wagon-Spigot 算法。它不需要浮点数学,并且主要是一种迭代方法。它确实需要一些内存来在中间计算中存储整数数组。

无需花时间精简或清理代码,这里是算法的实现(注意结果不添加小数点)。

如果您打算将此代码用于个人用途以外的任何用途,请务必引用算法和本网站。

C# 代码

public static string CalculatePi(int digits)
{   
    digits++;

    uint[] x = new uint[digits*10/3+2];
    uint[] r = new uint[digits*10/3+2];
    
    uint[] pi = new uint[digits];

    for (int j = 0; j < x.Length; j++)
        x[j] = 20;
        
    for (int i = 0; i < digits; i++)
    {
        uint carry = 0;
        for (int j = 0; j < x.Length; j++)
        {
            uint num = (uint)(x.Length - j - 1);
            uint dem = num * 2 + 1;

            x[j] += carry;

            uint q = x[j] / dem;
            r[j] = x[j] % dem;

            carry = q * num;
        }
        
        
        pi[i] = (x[x.Length-1] / 10);
            
                    
        r[x.Length - 1] = x[x.Length - 1] % 10; ;
        
        for (int j = 0; j < x.Length; j++)
            x[j] = r[j] * 10;
    }
    
    var result = "";
    
    uint c = 0;
    
    for(int i = pi.Length - 1; i >=0; i--)
    {
        pi[i] += c;
        c = pi[i] / 10;
        
        result = (pi[i] % 10).ToString() + result;
    }

    return result;
}

更新

我终于开始解决 35 位数字后发生的“进位错误”。实际上,链接文档的第 6 页专门讨论了这里发生的情况。我已经测试了 1000 位数的最终版本。

于 2012-07-26T22:32:32.440 回答
7
Math.Round(Math.PI, places)

如果您需要更高的精度,您将无法使用双精度数据类型,因为它支持某个最大值。精度(由 Math.PI 提供)。

于 2012-07-26T20:21:39.590 回答
7

经过大量搜索,我发现了这个小片段:

public static class BigMath
{
    // digits = number of digits to calculate;
    // iterations = accuracy (higher the number the more accurate it will be and the longer it will take.)
    public static BigInteger GetPi(int digits, int iterations)
    {
        return 16 * ArcTan1OverX(5, digits).ElementAt(iterations)
            - 4 * ArcTan1OverX(239, digits).ElementAt(iterations);
    }

    //arctan(x) = x - x^3/3 + x^5/5 - x^7/7 + x^9/9 - ...
    public static IEnumerable<BigInteger> ArcTan1OverX(int x, int digits)
    {
        var mag = BigInteger.Pow(10, digits);
        var sum = BigInteger.Zero;
        bool sign = true;
        for (int i = 1; true; i += 2)
        {
            var cur = mag / (BigInteger.Pow(x, i) * i);
            if (sign)
            {
                sum += cur;
            }
            else
            {
                sum -= cur;
            }
            yield return sum;
            sign = !sign;
        }
    }
}

到目前为止,它就像一个魅力。您只需从 GAC 添加 System.Numerics 库即可解析 BigInteger 类型。

于 2012-07-26T23:13:57.533 回答
7

与 nicholas 相同的算法,但使用 yield 进行惰性求值

    static public IEnumerable<uint> Pi()
    {
        uint[] x = new uint[short.MaxValue];
        uint[] r = new uint[short.MaxValue];

        for (int j = 0; j < short.MaxValue; j++)
            x[j] = 20;

        for (int i = 0; i < short.MaxValue; i++)
        {
            uint carry = 0;
            for (int j = 0; j < x.Length; j++)
            {
                uint num = (uint)(x.Length - j - 1);
                uint dem = num * 2 + 1;

                x[j] += carry;

                uint q = x[j] / dem;
                r[j] = x[j] % dem;

                carry = q * num;
            }

            yield return (x[x.Length - 1] / 10);

            r[x.Length - 1] = x[x.Length - 1] % 10; ;
            for (int j = 0; j < x.Length; j++)
            {
                x[j] = r[j] * 10;
            }                    
        }
    }

我使用 short.MaxValue 作为位置数的上限,但那是因为我的机器虚拟内存不足。更好的机器应该能够容纳最多 int.MaxValue。

该函数可以这样调用:

 class Program
{
    static void Main(string[] args)
    {
        foreach (uint digit in Calculator.Pi().Take(100))
        {
            Console.WriteLine(digit);
        }

        Console.Read();
    }
}
于 2015-01-04T15:18:58.300 回答
4

如果你对原生数学库提供的位数感到满意,那很简单;只需四舍五入到所需的位数。如果您需要更多数字(数十、数百或数千),则需要一种每次吐出一个数字的插口算法。Jeremy Gibbons 给出了一个算法,我在我的博客中实现 了两次,在那里你可以找到用 Scheme、C、Python、Haskell、Perl 和 Forth 编写的代码(但不是 C#,抱歉)。

于 2012-07-26T20:41:00.093 回答
2

最简单的方法是将大量数字 pi 存储在字符串常量中。然后,每当您需要n精确的数字时,您只需从 0 到n+2.

于 2012-07-26T21:53:56.457 回答