如何计算圆周率(π)?

39

如何编写一个函数,以给定的小数位数返回圆周率(π)?

速度不是问题。我一直在查看http://bellard.org/pi/,但我仍然不明白如何获取π的第n位数字。


11
http://en.wikipedia.org/wiki/Pi是一个很好的起点。 - Bill
1
另外,您需要计算圆周率还是仅格式化圆周率? - Bill
你可以看一下Pi计算记录http://bellard.org/pi/pi2700e9/,只是为了好玩。 - LB40
4
在一个交替出现正负项的收敛级数中,该级数会在目标值上下交替变化。因此,你可以知道该值总是在第n项和第n+1项之间。如果第n项和第n+1项的前k个数字相同,则你可以确定目标值的前k位数字。简而言之,当第一次出现前k位数字不再改变时,你就可以得到k位数字的精度。(非交替级数的计算方法有所不同。) - Alan
1
这种类型的更多示例请参见:http://en.wikipedia.org/wiki/Convergent_series - Alan
显示剩余3条评论
11个回答

31

在微积分中,有一个叫做泰勒级数的东西,可以提供一种简单的方法来计算许多无理数到任意精度。

π/4 = 1 - 1/3 + 1/5 - 1/7 + ...
(来源:http://www.math.hmc.edu/funfacts/ffiles/30001.1-3.shtml)

继续添加这些项,直到您想要的精度位数稳定为止。

泰勒定理是一个强大的工具,但使用该定理导出该级数超出了问题的范围。如果您对更多细节感兴趣,它是标准的大学第一年微积分知识,并且可以轻松通过谷歌搜索获得。


我并不是想暗示这是计算π最实用的方法。那将取决于您真正需要计算π的原因。为实际目的,您应该直接从已经发布的版本中复制所需数量的数字。我之所以建议这么做,是为了简单介绍如何将无理数等同于无限级数。


31
泰勒级数可能是在计算机上生成圆周率最糟糕的方法之一。您必须对计算进行高度精确,并且需要进行数十亿次迭代才能超过3.14159。您可以试试看。或者这里有一个网站介绍这个问题:http://www.cygnus-software.com/misc/pidigits.htm - indiv
2
@kts:这仍然无法改变您需要进行数十亿次迭代才能得出合理准确答案的事实。 - Billy ONeal
6
简单来说,注意到这些项正在逐渐变小,并且交替相加和相减。因此,当你添加一个项时,你肯定会高于真实的π。当你减去一个项时,你肯定低于真实的π。因此,最后两个部分和提供了真实值的上限和下限。 - slacker
8
@Alan:好的,但问题明确指出他正在尝试编写一个可以计算到小数点后X位的PI值的函数......无论如何,我实现了这个泰勒级数,在10亿次迭代后你得到了“3.14159265”。在30亿次迭代后,你得到了下一位数字。再过99亿次迭代后才会得到下一位数字。99亿。 - indiv
2
是的,如果你想要n位小数的精度,你需要大约进行10^n次迭代。我并不否认它收敛得很慢。原帖作者似乎对学习练习感兴趣,而不是实际应用。 - Alan
显示剩余7条评论

7
尝试使用“在O(n ^ 2)中计算任何基数下pi的第n位数字”的算法。这可能是已知的最快算法,它不需要任意(读作巨大的)精度浮点数,并且可以直接以十进制(或其他任何进制)给出结果。请访问此链接

6
作为JeffH方法的替代方案,您可以只存储最大位数并截取不需要的部分:
#include <string>
#include <iostream>
using std::cout; using std::endl; using std::string;

// The first 99 decimal digits taken from:
// http://www.geom.uiuc.edu/~huberty/math5337/groupe/digits.html
// Add more as needed.
const string pi =
  "1415926535"
  "8979323846"
  "2643383279"
  "5028841971"
  "6939937510"
  "5820974944"
  "5923078164"
  "0628620899"
  "8628034825"
  "342117067";

// A function in C++ that returns pi to X places
string CalcPi(const size_t decimalDigitsCount) 
{
  string returnValue = "3";
  if (decimalDigitsCount > 0)
  {
    returnValue += "." + pi.substr(0, decimalDigitsCount);
  }
  return returnValue;
} 

int main()
{
  // Loop through all the values of "pi at x digits" that we have. 
  for (size_t i = 0; i <= pi.size(); ++i) 
  {
    cout << "pi(" << i << "): " << CalcPi(i) << endl;
  } 
}

http://codepad.org/6mqDa1zj


7
鉴于π不会改变,且精确到43位数字足以计算宇宙周长,容忍度为1个质子的宽度,这是相当合理的。不幸的是,问题是关于计算第X位数字,并不一定要获取X之前的数字,这在这里并不是一个解决方案,因为X可以是任何数! - Neil Trodden
“...of the universe”应该更正为“...可观测宇宙”。 - trincot

6
我相信您要找的算法就是所谓的"Spigot Algorithm"。其中一种特定的算法是BBP(Bailey-Borwein-Plouffe)公式。
我认为那就是您想要的内容。

4

我会从公式入手

pi = 16 arctan (1/5) - 4 arctan (1/239)

谷歌可以轻松地找到一个普通人能够理解的这个公式的证明,以及计算反正切函数的公式。这将使您能够轻松快速地计算几千个小数位的圆周率。


3

你愿意查找值而不是计算吗?

由于您没有明确指定函数必须计算值,因此如果您愿意对可以“计算”的数字数量设定上限,则以下是可能的解决方案:

// Initialize pis as far out as you want. 
// There are lots of places you can look up pi out to a specific # of digits.
double pis[] = {3.0, 3.1, 3.14, 3.141, 3.1416}; 

/* 
 * A function that returns pi out to a number of digits (up to a point)
 */
double CalcPi(int x)
{
    // NOTE: Should add range checking here. For now, do not access past end of pis[]
    return pis[x]; 
}

int main()
{
    // Loop through all the values of "pi at x digits" that we have.
    for (int ii=0; ii<(int)sizeof(pis)/sizeof(double); ii++)
    {
        double piAtXdigits = CalcPi(ii);
    }
}

这样编写 CalcPi() 函数(如果符合你的需求)的好处是,在你的上限范围内,对于任何 X 值都同样非常快速。

1
这个解决方案可以与“计算解决方案”耦合:数组可以在.hpp中声明,并由生成相应.cpp文件的元编程定义(即填充)。元编程将由所需最大数字数量参数化,并计算值以将它们设置到数组中。 - Luc Touraille

3
"Mandelbrot集合中的π"探索了复平面上一系列点和计算它们的"Mandelbrot数"(缺乏更好的术语...确定序列中的点不是Mandelbrot集合成员所需的迭代次数)与π之间的奇特关系。
实用吗?可能不是。
意外和有趣吗?我认为是的。

2
我的见解...这可能不是最快的方法,但我认为它很容易理解。我在一次数学课上自己想出来的,我还没有在其他文献中看到过类似的。要么我是个天才,要么真的很蠢,或者根本不注意读数学书,或者以上都是...:)

无论如何...从单位圆开始。我们知道x ^ 2 + y ^ 2 = 1,因此y = sqrt(1-x ^ 2)。我们还知道单位圆的面积是PI。如果我们现在在范围0到1内对函数sqrt(1-x ^ 2)进行积分,我们将得到PI的四分之一。因此,将其乘以4即可得到PI:

PI formula

如果我们试图从分析角度解决这个问题,我相信我们只会得到圆周率。但是编写一个数值求解程序却非常容易。以下是一个用C语言编写的程序:
#include <math.h>
#include <stdio.h>

void main(void) {
    double interval=0.0000001,area=0,x,y;

    for (x=0; x<1; x+=interval)
        area+=4*interval*sqrt(1-x*x);

    printf("pi ~ %.20f\n",area);
}

使用上述的“间隔”设置运行它,我们得到:
pi ~ 3.14159285415672595576

所以1000万次迭代可以得到6位正确的小数。虽然不是最有效率的,但这是我的心血... :)

1
这是基于浮点数表现得像有理数的假设。这个假设是错误的。根据输入,此实现会产生极其不同的结果。结果的准确性仍然未知。 - IInspectable

0
pi = function () {
    let pi = 3;
    let a = 3;
    let even = false;
    let turn;

    while (a <= 90000) {
        turn = (4/((Math.pow(a, 3)) - (a)));

        if(even){
            turn = turn*-1;
            even = false;
        } else {
            even = true;
        }
        pi = pi + turn;
        a = a + 2;
    }
    return pi;
};

1
@t-arnold,你已经实现了这个函数,但是最好也能加上一些解释。 - Sergii

0
我能够计算出接近实际圆周率的PI。以下是它的C++实现代码。
#include <iostream>
#include <iomanip>
#include <cmath>

using namespace std;

long double calPI(long iterations = 1000)
{
    long double pi = 0.0, factor = 1;
    long i;
    for (i = 2; i < iterations; i += 2, factor *= -1)
    {
        pi += factor / (i - 1.0);
    }
    return 4 * pi;
}
// 3.14159265358979323846264338327950288419716939937510          -> Actual (50 digits)
// 3.141592653589793115997963468544185161590576171875            -> From acos
// 3.14159265452113118342880593303334535448811948299407958984375 -> Calculated
int main()
{
    int digits;
    long double PI;
    cout << "Enter how many digits of precision you want to have : ";
    cin >> digits;
    PI = calPI(2147453647); // By far the maximum precision I was able to reach
    cout << "The value of pi upto " << digits << " digits : " << setprecision(digits) << PI << endl;
    cout << "Actual value of pi from math.h : " << 2 * acos(0.0) << endl;
    return 0;
}

实际上,关于我使用的这个序列是如何推导出来的,这里有一个非常好的解释,我强烈建议你观看。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接