获得π值的最快方法是什么?


322

我正在寻找获得π值的最快方法,这是个人的挑战。更具体地说,我使用的方式不涉及使用#define诸如的常数,也不用M_PI硬编码数字。

下面的程序测试了我所知道的各种方式。理论上,内联汇编版本是最快的选择,尽管显然不是便携式的。我将其作为与其他版本进行比较的基准。在我的测试中,使用内置功能,该4 * atan(1)版本在GCC 4.2上运行速度最快,因为它可以将折叠atan(1)为常数。使用-fno-builtin指定的atan2(0, -1)版本是最快的。

这是主要的测试程序(pitimes.c):

#include <math.h>
#include <stdio.h>
#include <time.h>

#define ITERS 10000000
#define TESTWITH(x) {                                                       \
    diff = 0.0;                                                             \
    time1 = clock();                                                        \
    for (i = 0; i < ITERS; ++i)                                             \
        diff += (x) - M_PI;                                                 \
    time2 = clock();                                                        \
    printf("%s\t=> %e, time => %f\n", #x, diff, diffclock(time2, time1));   \
}

static inline double
diffclock(clock_t time1, clock_t time0)
{
    return (double) (time1 - time0) / CLOCKS_PER_SEC;
}

int
main()
{
    int i;
    clock_t time1, time2;
    double diff;

    /* Warmup. The atan2 case catches GCC's atan folding (which would
     * optimise the ``4 * atan(1) - M_PI'' to a no-op), if -fno-builtin
     * is not used. */
    TESTWITH(4 * atan(1))
    TESTWITH(4 * atan2(1, 1))

#if defined(__GNUC__) && (defined(__i386__) || defined(__amd64__))
    extern double fldpi();
    TESTWITH(fldpi())
#endif

    /* Actual tests start here. */
    TESTWITH(atan2(0, -1))
    TESTWITH(acos(-1))
    TESTWITH(2 * asin(1))
    TESTWITH(4 * atan2(1, 1))
    TESTWITH(4 * atan(1))

    return 0;
}

内联汇编材料(fldpi.c)仅适用于x86和x64系统:

double
fldpi()
{
    double pi;
    asm("fldpi" : "=t" (pi));
    return pi;
}

还有一个构建脚本,用于构建我正在测试的所有配置(build.sh):

#!/bin/sh
gcc -O3 -Wall -c           -m32 -o fldpi-32.o fldpi.c
gcc -O3 -Wall -c           -m64 -o fldpi-64.o fldpi.c

gcc -O3 -Wall -ffast-math  -m32 -o pitimes1-32 pitimes.c fldpi-32.o
gcc -O3 -Wall              -m32 -o pitimes2-32 pitimes.c fldpi-32.o -lm
gcc -O3 -Wall -fno-builtin -m32 -o pitimes3-32 pitimes.c fldpi-32.o -lm
gcc -O3 -Wall -ffast-math  -m64 -o pitimes1-64 pitimes.c fldpi-64.o -lm
gcc -O3 -Wall              -m64 -o pitimes2-64 pitimes.c fldpi-64.o -lm
gcc -O3 -Wall -fno-builtin -m64 -o pitimes3-64 pitimes.c fldpi-64.o -lm

除了在各种编译器标记之间进行测试(我也将32位与64位进行了比较,因为优化方式不同)之外,我还尝试过切换测试的顺序。但是,该atan2(0, -1)版本仍然每次都能脱颖而出。


38
在C ++元编程中必须有一种方法可以做到这一点。运行时间会非常好,但是编译时间不会。
David Thornley,2009年

1
为什么您考虑使用不同于M_PI的atan(1)?如果您仅使用算术运算,我会理解为什么要这样做,但是对于atan来说,我并不明白这一点。
erikkallen

9
问题是:为什么你会不会想用一个常数?例如,是由图书馆定义还是由您自己定义?计算Pi浪费了CPU周期,因为这个问题已经一遍又一遍地解决了很多有效数字,远远超过了日常计算所需的位数
Tilo

2
@ HopelessN00b在我说的英语方言中,“优化”是用“ s”而不是“ z” 拼写的(BTW的发音是“ zed”,不是“ zee” ;-))。(如果您查看审阅历史记录,这也不是我第一次也必须还原这种编辑。)
克里斯·杰斯特·杨

Answers:


205

蒙特卡罗方法,如前所述,适用于一些伟大的概念,但它是,显然,不是最快的,而不是由一个长镜头,没有任何合理的措施。而且,这完全取决于您要寻找的精度。我知道的最快的π是经过硬编码的数字。看PiPi [PDF],有很多公式。

这是一种快速收敛的方法-每次迭代约14位。当前最快的应用程序PiFast将这个公式与FFT一起使用。因为代码很简单,所以我只写公式。这个公式几乎是Ramanujan发现的,而Chudnovsky发现的。实际上,这就是他计算出数十亿个数字的方式,因此这不是一种无视的方法。该公式将很快溢出,并且由于我们正在分解阶乘,因此延迟此类计算以删除条款将是有利的。

在此处输入图片说明

在此处输入图片说明

哪里,

在此处输入图片说明

下面是Brent–Salamin算法。维基百科提到,当ab “足够接近”时,(a + b)²/ 4t将近似为π。我不确定“足够接近”是什么意思,但是从我的测试中,一次迭代得到2位数字,两次得到7位,而三次得到15位,当然这是双精度的,因此根据其表示形式可能会有错误。在真正的计算可以更准确。

let pi_2 iters =
    let rec loop_ a b t p i =
        if i = 0 then a,b,t,p
        else
            let a_n = (a +. b) /. 2.0 
            and b_n = sqrt (a*.b)
            and p_n = 2.0 *. p in
            let t_n = t -. (p *. (a -. a_n) *. (a -. a_n)) in
            loop_ a_n b_n t_n p_n (i - 1)
    in 
    let a,b,t,p = loop_ (1.0) (1.0 /. (sqrt 2.0)) (1.0/.4.0) (1.0) iters in
    (a +. b) *. (a +. b) /. (4.0 *. t)

最后,打高尔夫(800位数)怎么样?160个字符!

int a=10000,b,c=2800,d,e,f[2801],g;main(){for(;b-c;)f[b++]=a/5;for(;d=0,g=c*2;c-=14,printf("%.4d",e+d/a),e=d%a)for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);}

1
假设您正在尝试自己实施第一个,则sqr(k3)不会有问题吗?我非常确定它最终将导致您必须估计一个不合理的数字(IIRC,所有不是整数的根都是不合理的)。如果您使用的是无限精度算术,则所有其他内容看起来都非常简单明了,但该平方根却是一个交易破坏者。第二个也包括sqrt。
Bill K

2
根据我的经验,“足够接近”通常意味着涉及泰勒级数逼近。
斯蒂芬,

117

我真的很喜欢这个程序,因为它通过查看自己的区域来近似π。

IOCCC 1988:westley.c

#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

1
如果将_替换为-F <00 || --F-OO--应该更容易理解:-)
Pat

1
或者,如果将_替换为“如果(以前的字符为'-'){OO--;} F--;”
FryGuy

6
它在此处打印0.25 -.-
Johannes Schaub-litb

8
该程序在1998年很棒,但由于现代预处理器在宏扩展周围插入空格以防止此类工作而变得更宽松,因此遭到了破坏。不幸的是,这是一个遗物。
克里斯·卢茨

38
传递--traditional-cppcpp以获得预期的行为。
尼采茹

78

这是我在高中学习的用于计算pi的技术的一般描述。

我之所以只分享这一点,是因为我认为它很简单,任何人都可以无限期地记住它,此外它还教给您“蒙特卡洛”方法的概念-这是统计方法,可以得出并非立即出现的答案可通过随机过程推导。

绘制一个正方形,然后在该正方形内刻上一个象限(半圆的四分之一)(一个象限,其半径等于正方形的边,因此它会尽可能多地填充正方形)

现在将飞镖扔到广场上,并​​记录下它的着陆位置,即在广场内的任意位置选择一个随机点。当然,它降落在正方形内部,但是它在半圆形内部吗?记录这个事实。

重复此过程多次-您会发现半圆内的点数与所抛出的总数之比,​​称之为x。

由于正方形的面积是r乘以r,因此可以推断出半圆的面积是x乘以r乘以r(即x乘以r的平方)。因此,x乘以4将得到pi。

这不是一种快速使用的方法。但这是蒙特卡洛方法的一个很好的例子。而且,如果环顾四周,您可能会发现许多其他方法无法解决的问题都可以通过此类方法解决。


2
这是我们在学校的Java项目中用来计算Pi的方法。只是使用一个随机化器得出x,y坐标,我们越靠近Pi越扔“飞镖”。
Jeff Keslinke,09年

55

为了完整起见,有一个C ++模板版本,为了优化构建,该版本将在编译时计算PI的近似值,并内联为单个值。

#include <iostream>

template<int I>
struct sign
{
    enum {value = (I % 2) == 0 ? 1 : -1};
};

template<int I, int J>
struct pi_calc
{
    inline static double value ()
    {
        return (pi_calc<I-1, J>::value () + pi_calc<I-1, J+1>::value ()) / 2.0;
    }
};

template<int J>
struct pi_calc<0, J>
{
    inline static double value ()
    {
        return (sign<J>::value * 4.0) / (2.0 * J + 1.0) + pi_calc<0, J-1>::value ();
    }
};


template<>
struct pi_calc<0, 0>
{
    inline static double value ()
    {
        return 4.0;
    }
};

template<int I>
struct pi
{
    inline static double value ()
    {
        return pi_calc<I, I>::value ();
    }
};

int main ()
{
    std::cout.precision (12);

    const double pi_value = pi<10>::value ();

    std::cout << "pi ~ " << pi_value << std::endl;

    return 0;
}

请注意,对于I> 10,优化的构建可能会很慢,对于非优化的运行也是如此。我相信对于12次迭代,大约有80k次对value()的调用(在没有备忘录的情况下)。


我运行此文件,得到“
pi〜3.14159265383

5
好吧,这对9dp来说是准确的。您是反对还是只是观察?
jon-hanson 2010年

此处用于计算PI的算法的名称是什么?
塞巴斯蒂昂·米兰达

1
@sebastião-mirandaLeibniz 的公式具有平均加速能力,可提高收敛性。pi_calc<0, J>从公式计算每个连续项,非专业化pi_calc<I, J>计算平均值。
jon-hanson '16

43

实际上,有一整本书专门介绍了用于计算\ pi的快速方法:“ Pi and AGM”,作者是Jonathan和Peter Borwein(可在亚马逊上找到)。

我对AGM和相关算法进行了很多研究:这很有趣(尽管有时很简单)。

请注意,要实现大多数现代算法来计算\ pi,您将需要一个多精度算术库(GMP是一个不错的选择,尽管自从我上次使用它已经有一段时间了)。

最佳算法的时间复杂度在O(M(n)log(n))中,其中M(n)是两个n位整数相乘的时间复杂度(M(n)= O(n log(n)log(log(n)))使用基于FFT的算法,这通常在计算\ pi的数字时需要,并且这种算法在GMP中实现。

请注意,即使算法背后的数学方法可能并非无关紧要,但算法本身通常是几行伪代码,并且它们的实现通常非常简单(如果您选择不编写自己的多精度算法:-))。


42

以下精确地回答了如何以最快的方式-最少的计算工作来做到这一点。即使您不喜欢答案,也必须承认这确实是获得PI价值的最快方法。

最快获得Pi值的方法是:

1)选择您喜欢的编程语言2)加载其数学库3)并发现Pi已在此处定义-可供使用!

如果您手边没有数学库。

第二快的方式(更通用的解决方案)是:

在Internet上查找Pi,例如:

http://www.eveandersson.com/pi/digits/1000000(1百万个数字..您的浮点精度是多少?)

或在这里:

http://3.141592653589793238462643383279502884197169399375105820974944592.com/

或在这里:

http://en.wikipedia.org/wiki/Pi

找到想要使用的任何精度算术所需的数字确实非常快,并且通过定义一个常数,可以确保您不会浪费宝贵的CPU时间。

这不仅是部分幽默的答案,而且在现实中,如果有人继续进行计算,并在实际应用中计算Pi的值,那将浪费大量的CPU时间,不是吗?至少我没有看到真正的应用程序来尝试重新计算它。

尊敬的主持人:请注意,操作员问:“获取PI价值的最快方法”


尊敬的提洛:亲爱的提请注意,OP表示:“我正在寻求获得π值的最快方法,这是个人的挑战。更具体地说,我正在使用不涉及使用#define常量的方法,例如M_PI或硬编码中的数字
最大

尊敬的@Max:请注意,OP 回答后就编辑了原始问题-这几乎不是我的错;)我的解决方案仍然是最快的方法,并且以任何所需的浮点精度且没有CPU周期的优雅方式解决了问题:)
提洛

哦,对不起,我没有意识到。只是想一想,硬编码常数的精度是否会比计算pi的精度低?我猜这取决于它是什么语言,以及创作者是否愿意将所有数字放入:-)
Max

1
该死,我忘了添加Dear Tilo
Max

27

BBP公式允许你计算第n个数字-在基体2(或16) - ,而无需甚至与前n-1个位打扰第一:)


23

我始终使用而不是将pi定义为常量acos(-1)


2
cos(-1)还是acos(-1)?:-P(后者)是我原始代码中的测试用例之一。这是我的首选(与atan2(0,-1)一起,它与acos(-1)确实一样,除了acos通常是根据atan2实现的),但是有些编译器针对4 * atan(1)进行了优化。 !
克里斯·杰斯特·杨(

21

这是一种“经典”方法,非常易于实现。使用python(不是最快的语言)的此实现可以做到:

from math import pi
from time import time


precision = 10**6 # higher value -> higher precision
                  # lower  value -> higher speed

t = time()

calc = 0
for k in xrange(0, precision):
    calc += ((-1)**k) / (2*k+1.)
calc *= 4. # this is just a little optimization

t = time()-t

print "Calculated: %.40f" % calc
print "Constant pi: %.40f" % pi
print "Difference: %.40f" % abs(calc-pi)
print "Time elapsed: %s" % repr(t)

您可以在此处找到更多信息。

无论如何,在python中获取pi尽可能多的精确值的最快方法是:

from gmpy import pi
print pi(3000) # the rule is the same as 
               # the precision on the previous code

这是gmpy pi方法的源代码,在这种情况下,我认为代码不如注释有用:

static char doc_pi[]="\
pi(n): returns pi with n bits of precision in an mpf object\n\
";

/* This function was originally from netlib, package bmp, by
 * Richard P. Brent. Paulo Cesar Pereira de Andrade converted
 * it to C and used it in his LISP interpreter.
 *
 * Original comments:
 * 
 *   sets mp pi = 3.14159... to the available precision.
 *   uses the gauss-legendre algorithm.
 *   this method requires time o(ln(t)m(t)), so it is slower
 *   than mppi if m(t) = o(t**2), but would be faster for
 *   large t if a faster multiplication algorithm were used
 *   (see comments in mpmul).
 *   for a description of the method, see - multiple-precision
 *   zero-finding and the complexity of elementary function
 *   evaluation (by r. p. brent), in analytic computational
 *   complexity (edited by j. f. traub), academic press, 1976, 151-176.
 *   rounding options not implemented, no guard digits used.
*/
static PyObject *
Pygmpy_pi(PyObject *self, PyObject *args)
{
    PympfObject *pi;
    int precision;
    mpf_t r_i2, r_i3, r_i4;
    mpf_t ix;

    ONE_ARG("pi", "i", &precision);
    if(!(pi = Pympf_new(precision))) {
        return NULL;
    }

    mpf_set_si(pi->f, 1);

    mpf_init(ix);
    mpf_set_ui(ix, 1);

    mpf_init2(r_i2, precision);

    mpf_init2(r_i3, precision);
    mpf_set_d(r_i3, 0.25);

    mpf_init2(r_i4, precision);
    mpf_set_d(r_i4, 0.5);
    mpf_sqrt(r_i4, r_i4);

    for (;;) {
        mpf_set(r_i2, pi->f);
        mpf_add(pi->f, pi->f, r_i4);
        mpf_div_ui(pi->f, pi->f, 2);
        mpf_mul(r_i4, r_i2, r_i4);
        mpf_sub(r_i2, pi->f, r_i2);
        mpf_mul(r_i2, r_i2, r_i2);
        mpf_mul(r_i2, r_i2, ix);
        mpf_sub(r_i3, r_i3, r_i2);
        mpf_sqrt(r_i4, r_i4);
        mpf_mul_ui(ix, ix, 2);
        /* Check for convergence */
        if (!(mpf_cmp_si(r_i2, 0) && 
              mpf_get_prec(r_i2) >= (unsigned)precision)) {
            mpf_mul(pi->f, pi->f, r_i4);
            mpf_div(pi->f, pi->f, r_i3);
            break;
        }
    }

    mpf_clear(ix);
    mpf_clear(r_i2);
    mpf_clear(r_i3);
    mpf_clear(r_i4);

    return (PyObject*)pi;
}

编辑:我在剪切和粘贴以及压痕方面遇到了一些问题,您可以在此处找到源。



18

如果您愿意使用近似值,355 / 113则可以使用6个十进制数字,并且具有可以与整数表达式一起使用的附加优点。如今,这已经不那么重要了,因为“浮点数学协处理器”不再具有任何意义,但是曾经很重要。


18

使用类似Machin的公式

176 * arctan (1/57) + 28 * arctan (1/239) - 48 * arctan (1/682) + 96 * arctan(1/12943) 

[; \left( 176 \arctan \frac{1}{57} + 28 \arctan \frac{1}{239} - 48 \arctan \frac{1}{682} + 96 \arctan \frac{1}{12943}\right) ;], for you TeX the World people.

在Scheme中实施,例如:

(+ (- (+ (* 176 (atan (/ 1 57))) (* 28 (atan (/ 1 239)))) (* 48 (atan (/ 1 682)))) (* 96 (atan (/ 1 12943))))


16

与双打:

4.0 * (4.0 * Math.Atan(0.2) - Math.Atan(1.0 / 239.0))

精确到小数点后14位,足以填充双精度(不准确可能是因为反正切中的其余小数点被截断了)。

还有塞思,它是3.14159265358979323846 3,而不是64。


16

Pi正好是3![教授 皱纹(辛普森一家)]

开个玩笑,但这是C#中的一个(需要.NET-Framework)。

using System;
using System.Text;

class Program {
    static void Main(string[] args) {
        int Digits = 100;

        BigNumber x = new BigNumber(Digits);
        BigNumber y = new BigNumber(Digits);
        x.ArcTan(16, 5);
        y.ArcTan(4, 239);
        x.Subtract(y);
        string pi = x.ToString();
        Console.WriteLine(pi);
    }
}

public class BigNumber {
    private UInt32[] number;
    private int size;
    private int maxDigits;

    public BigNumber(int maxDigits) {
        this.maxDigits = maxDigits;
        this.size = (int)Math.Ceiling((float)maxDigits * 0.104) + 2;
        number = new UInt32[size];
    }
    public BigNumber(int maxDigits, UInt32 intPart)
        : this(maxDigits) {
        number[0] = intPart;
        for (int i = 1; i < size; i++) {
            number[i] = 0;
        }
    }
    private void VerifySameSize(BigNumber value) {
        if (Object.ReferenceEquals(this, value))
            throw new Exception("BigNumbers cannot operate on themselves");
        if (value.size != this.size)
            throw new Exception("BigNumbers must have the same size");
    }

    public void Add(BigNumber value) {
        VerifySameSize(value);

        int index = size - 1;
        while (index >= 0 && value.number[index] == 0)
            index--;

        UInt32 carry = 0;
        while (index >= 0) {
            UInt64 result = (UInt64)number[index] +
                            value.number[index] + carry;
            number[index] = (UInt32)result;
            if (result >= 0x100000000U)
                carry = 1;
            else
                carry = 0;
            index--;
        }
    }
    public void Subtract(BigNumber value) {
        VerifySameSize(value);

        int index = size - 1;
        while (index >= 0 && value.number[index] == 0)
            index--;

        UInt32 borrow = 0;
        while (index >= 0) {
            UInt64 result = 0x100000000U + (UInt64)number[index] -
                            value.number[index] - borrow;
            number[index] = (UInt32)result;
            if (result >= 0x100000000U)
                borrow = 0;
            else
                borrow = 1;
            index--;
        }
    }
    public void Multiply(UInt32 value) {
        int index = size - 1;
        while (index >= 0 && number[index] == 0)
            index--;

        UInt32 carry = 0;
        while (index >= 0) {
            UInt64 result = (UInt64)number[index] * value + carry;
            number[index] = (UInt32)result;
            carry = (UInt32)(result >> 32);
            index--;
        }
    }
    public void Divide(UInt32 value) {
        int index = 0;
        while (index < size && number[index] == 0)
            index++;

        UInt32 carry = 0;
        while (index < size) {
            UInt64 result = number[index] + ((UInt64)carry << 32);
            number[index] = (UInt32)(result / (UInt64)value);
            carry = (UInt32)(result % (UInt64)value);
            index++;
        }
    }
    public void Assign(BigNumber value) {
        VerifySameSize(value);
        for (int i = 0; i < size; i++) {
            number[i] = value.number[i];
        }
    }

    public override string ToString() {
        BigNumber temp = new BigNumber(maxDigits);
        temp.Assign(this);

        StringBuilder sb = new StringBuilder();
        sb.Append(temp.number[0]);
        sb.Append(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator);

        int digitCount = 0;
        while (digitCount < maxDigits) {
            temp.number[0] = 0;
            temp.Multiply(100000);
            sb.AppendFormat("{0:D5}", temp.number[0]);
            digitCount += 5;
        }

        return sb.ToString();
    }
    public bool IsZero() {
        foreach (UInt32 item in number) {
            if (item != 0)
                return false;
        }
        return true;
    }

    public void ArcTan(UInt32 multiplicand, UInt32 reciprocal) {
        BigNumber X = new BigNumber(maxDigits, multiplicand);
        X.Divide(reciprocal);
        reciprocal *= reciprocal;

        this.Assign(X);

        BigNumber term = new BigNumber(maxDigits);
        UInt32 divisor = 1;
        bool subtractTerm = true;
        while (true) {
            X.Divide(reciprocal);
            term.Assign(X);
            divisor += 2;
            term.Divide(divisor);
            if (term.IsZero())
                break;

            if (subtractTerm)
                this.Subtract(term);
            else
                this.Add(term);
            subtractTerm = !subtractTerm;
        }
    }
}

15

使用D在编译时计算PI。

(从DSource.org复制)

/** Calculate pi at compile time
 *
 * Compile with dmd -c pi.d
 */
module calcpi;

import meta.math;
import meta.conv;

/** real evaluateSeries!(real x, real metafunction!(real y, int n) term)
 *
 * Evaluate a power series at compile time.
 *
 * Given a metafunction of the form
 *  real term!(real y, int n),
 * which gives the nth term of a convergent series at the point y
 * (where the first term is n==1), and a real number x,
 * this metafunction calculates the infinite sum at the point x
 * by adding terms until the sum doesn't change any more.
 */
template evaluateSeries(real x, alias term, int n=1, real sumsofar=0.0)
{
  static if (n>1 && sumsofar == sumsofar + term!(x, n+1)) {
     const real evaluateSeries = sumsofar;
  } else {
     const real evaluateSeries = evaluateSeries!(x, term, n+1, sumsofar + term!(x, n));
  }
}

/*** Calculate atan(x) at compile time.
 *
 * Uses the Maclaurin formula
 *  atan(z) = z - z^3/3 + Z^5/5 - Z^7/7 + ...
 */
template atan(real z)
{
    const real atan = evaluateSeries!(z, atanTerm);
}

template atanTerm(real x, int n)
{
    const real atanTerm =  (n & 1 ? 1 : -1) * pow!(x, 2*n-1)/(2*n-1);
}

/// Machin's formula for pi
/// pi/4 = 4 atan(1/5) - atan(1/239).
pragma(msg, "PI = " ~ fcvt!(4.0 * (4*atan!(1/5.0) - atan!(1/239.0))) );

2
不幸的是,切线是基于pi的反正切,在某种程度上使该计算无效。
格兰特·约翰逊

14

这个版本(在Delphi中)没什么特别的,但是至少比Nick Hodge在他的博客 :) 上发布的版本要快。在我的机器上,执行十亿次迭代大约需要16秒,其值是3.14159265 25879(准确的部分以粗体显示)。

program calcpi;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  start, finish: TDateTime;

function CalculatePi(iterations: integer): double;
var
  numerator, denominator, i: integer;
  sum: double;
begin
  {
  PI may be approximated with this formula:
  4 * (1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 .......)
  //}
  numerator := 1;
  denominator := 1;
  sum := 0;
  for i := 1 to iterations do begin
    sum := sum + (numerator/denominator);
    denominator := denominator + 2;
    numerator := -numerator;
  end;
  Result := 4 * sum;
end;

begin
  try
    start := Now;
    WriteLn(FloatToStr(CalculatePi(StrToInt(ParamStr(1)))));
    finish := Now;
    WriteLn('Seconds:' + FormatDateTime('hh:mm:ss.zz',finish-start));
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

13

早在过去,单词大小较小且浮点运算缓慢或不存在,我们曾经做过这样的事情:

/* Return approximation of n * PI; n is integer */
#define pi_times(n) (((n) * 22) / 7)

对于不需要很多精度的应用程序(例如,视频游戏),这非常快且足够准确。


11
为了更准确的使用355 / 113。对于涉及的数字大小非常准确。
David Thornley,2009年

只是出于好奇:7分之223 + 1/7
Agnius Vasiliauskas

13

如果要计算 π值的近似值(由于某种原因),则应尝试使用二进制提取算法。BellardBBP 改进使PI达到O(N ^ 2)。


如果要获得 π值的近似值以进行计算,则:

PI = 3.141592654

当然,这只是一个近似值,并不完全准确。差额超过0.00000000004102。(四十分之一万分之一,约4 / 10,000,000,000)。


如果要使用π 进行数学运算,则可以自己拿铅笔和纸或计算机代数包,并使用π的精确值π。

如果您真的想要一个公式,那么这个很有趣:

π= -i ln(-1)


您的公式取决于在复杂平面中定义ln的方式。它必须沿复数平面中的一条线不连续,并且该线通常为负实轴。
erikkallen

12

克里斯在上面发布的布伦特方法非常好。布伦特通常是任意精度算术领域的巨头。

如果您想要的只是第N个数字,那么著名的 BBP公式 可用于十六进制


1
我没有发布Brent方法;它是由安德里亚(Andrea)发布的,而我恰好是最后编辑该帖子的人。:-)但我同意,该职位值得推荐。
克里斯·杰斯特·杨(

1

从圆面积计算π:-)

<input id="range" type="range" min="10" max="960" value="10" step="50" oninput="calcPi()">
<br>
<div id="cont"></div>

<script>
function generateCircle(width) {
    var c = width/2;
    var delta = 1.0;
    var str = "";
    var xCount = 0;
    for (var x=0; x <= width; x++) {
        for (var y = 0; y <= width; y++) {
            var d = Math.sqrt((x-c)*(x-c) + (y-c)*(y-c));
            if (d > (width-1)/2) {
                str += '.';
            }
            else {
                xCount++;
                str += 'o';
            }
            str += "&nbsp;" 
        }
        str += "\n";
    }
    var pi = (xCount * 4) / (width * width);
    return [str, pi];
}

function calcPi() {
    var e = document.getElementById("cont");
    var width = document.getElementById("range").value;
    e.innerHTML = "<h4>Generating circle...</h4>";
    setTimeout(function() {
        var circ = generateCircle(width);
        e.innerHTML  = "<pre>" + "π = " + circ[1].toFixed(2) + "\n" + circ[0] +"</pre>";
    }, 200);
}
calcPi();
</script>


0

如果您不介意执行平方根和几个逆运算,那么Chudnovsky算法将非常快。它仅需2次迭代即可收敛到双精度。

/*
    Chudnovsky algorithm for computing PI
*/

#include <iostream>
#include <cmath>
using namespace std;

double calc_PI(int K=2) {

    static const int A = 545140134;
    static const int B = 13591409;
    static const int D = 640320;

    const double ID3 = 1./ (double(D)*double(D)*double(D));

    double sum = 0.;
    double b   = sqrt(ID3);
    long long int p = 1;
    long long int a = B;

    sum += double(p) * double(a)* b;

    // 2 iterations enough for double convergence
    for (int k=1; k<K; ++k) {
        // A*k + B
        a += A;
        // update denominator
        b *= ID3;
        // p = (-1)^k 6k! / 3k! k!^3
        p *= (6*k)*(6*k-1)*(6*k-2)*(6*k-3)*(6*k-4)*(6*k-5);
        p /= (3*k)*(3*k-1)*(3*k-2) * k*k*k;
        p = -p;

        sum += double(p) * double(a)* b;
    }

    return 1./(12*sum);
}

int main() {

    cout.precision(16);
    cout.setf(ios::fixed);

    for (int k=1; k<=5; ++k) cout << "k = " << k << "   PI = " << calc_PI(k) << endl;

    return 0;
}

结果:

k = 1   PI = 3.1415926535897341
k = 2   PI = 3.1415926535897931
k = 3   PI = 3.1415926535897931
k = 4   PI = 3.1415926535897931
k = 5   PI = 3.1415926535897931

0

更好的方法

为了获得诸如pi或标准概念之类的标准常量的输出,我们应该首先使用您所使用的语言中可用的内建方法。它将以最快,最好的方式返回值。我正在使用python运行最快的方法来获取pi的值。

  • 数学库的pi变量。数学库将变量pi存储为常数。

math_pi.py

import math
print math.pi

使用Linux的time实用程序运行脚本 /usr/bin/time -v python math_pi.py

输出:

Command being timed: "python math_pi.py"
User time (seconds): 0.01
System time (seconds): 0.01
Percent of CPU this job got: 91%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.03
  • 使用Arc Cos数学方法

acos_pi.py

import math
print math.acos(-1)

使用Linux的time实用程序运行脚本 /usr/bin/time -v python acos_pi.py

输出:

Command being timed: "python acos_pi.py"
User time (seconds): 0.02
System time (seconds): 0.01
Percent of CPU this job got: 94%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.03

bbp_pi.py

from decimal import Decimal, getcontext
getcontext().prec=100
print sum(1/Decimal(16)**k * 
          (Decimal(4)/(8*k+1) - 
           Decimal(2)/(8*k+4) - 
           Decimal(1)/(8*k+5) -
           Decimal(1)/(8*k+6)) for k in range(100))

使用Linux的time实用程序运行脚本 /usr/bin/time -v python bbp_pi.py

输出:

Command being timed: "python c.py"
User time (seconds): 0.05
System time (seconds): 0.01
Percent of CPU this job got: 98%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.06

因此最好的方法是使用语言提供的内置方法,因为它们是最快,最好的输出方法。在python中使用math.pi

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.