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

e4yzc0pl  于 2022-09-21  发布在  Unix
关注(0)|答案(15)|浏览(313)

我正在寻找获得π价值的最快方法,作为个人挑战。更具体地说,我使用的方法不涉及使用#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("%st=> %e, time => %fn", #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;
}

以及仅适用于x86和x64系统的内联汇编内容(fldpi.c):

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)版本仍然每次都位居榜首。

h4cxqtbf

h4cxqtbf1#

如上所述,Monte Carlo method应用了一些伟大的概念,但显然,它不是最快的,也不是遥不可及的,也不是任何合理的衡量标准。此外,这完全取决于你想要的是什么样的准确性。我所知道的最快的π是硬编码的数字。看看PiPi[PDF],有很多公式。

这里有一种快速收敛的方法--每次迭代大约14位数字。目前速度最快的应用程序PiFast在FFT中使用了此公式。我只写公式,因为代码很简单。这个公式在Ramanujan and discovered by Chudnovsky几乎找到了。这实际上是他计算数十亿位数字的方法--所以这是一个不容忽视的方法。公式将很快溢出,由于我们正在划分阶乘,因此推迟计算以删除项将是有利的。

哪里,

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

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);}
bogh5gae

bogh5gae2#

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

IOCCC 1988:westley.c


# define _ -F<00||--F-OO--;

int F=00,OO=00;main(){F_OO();printf("%1.3fn",4.*-F/OO/OO);}F_OO()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}
92dk7w1h

92dk7w1h3#

以下是我在高中学到的一种计算圆周率的技术的总体描述。

我之所以分享这一点,是因为我认为它足够简单,任何人都可以无限期地记住它,而且它还教会了你“蒙特卡洛”方法的概念--这是一种统计方法,可以得出看起来不是立即可以通过随机过程推断的答案。

画一个正方形,并在该正方形(半径等于正方形边的象限)内刻上一个象限(半圆的四分之一),这样它就可以填满尽可能多的正方形。

现在向正方形扔一个飞镖,并记录它落在哪里--也就是说,在正方形内的任何地方选择一个随机点。当然,它落在了正方形内,但它是在半圆形内吗?记录下这一事实。

重复这个过程很多次,你会发现半圆内的点数与抛出的总点数之比,称为x。

因为正方形的面积是r乘r,所以你可以推论出半圆的面积是x乘r乘r(即x乘r的平方)。因此,x乘以4会得到圆周率。

这不是一种快速使用的方法。但这是蒙特卡罗方法的一个很好的例子。如果你环顾四周,你会发现许多计算能力之外的问题都可以用这种方法解决。

ie3xauqp

ie3xauqp4#

出于完整性的考虑,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()的调用(在没有记忆的情况下)。

ikfrs5lh

ikfrs5lh5#

以下答案准确地回答了如何以尽可能快的方式--以最少的计算工作量做到这一点。即使你不喜欢这个答案,你也不得不承认,这确实是获得PI值的最快方法。

获取PI值的最快方法是:

1.选择你最喜欢的编程语言
1.加载其数学库
1.发现已经在那里定义了PI--可以使用了!

如果你手头没有数学库..

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

在互联网上查找PI,例如:

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

或者在这里:

http://3.141592653589793238462643383279502884197169399375105820974944592.com/

或者在这里:

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

无论您想要使用什么精度的算术,找到所需的位数都非常快,并且通过定义一个常量,您可以确保不会浪费宝贵的CPU时间。

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

还要考虑NASA在计算星际旅行时只使用15位PI:

尊敬的版主:请注意,操作员问:“以最快的方式获得PI的价值”

wlzqhblo

wlzqhblo6#

实际上,有一整本书(其中包括)专门介绍计算圆周率的“快速”方法:乔纳森和彼得·博尔文(available on Amazon)的“圆周率与年率”(Pi And The AGM)。

我对年度股东大会和相关算法研究了很多:它很有趣(尽管有时不是微不足道的)。

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

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

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

ni65a41a

ni65a41a7#

BBP formula允许您计算第n位-以2(或16)为基数-甚至不必费心先计算前n-1位:)

carvr3hs

carvr3hs8#

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

yi0zb3m4

yi0zb3m49#

这是一种“经典”方法,非常容易实现。这个用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)

您可以找到更多信息here

无论如何,要获得您想要的精确的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 objectn
";

/* 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;
}

**编辑:**我在剪切粘贴和缩进方面遇到了一些问题,你可以找到源代码here

ou6hu8tu

ou6hu8tu10#

如果您所说的最快是指输入代码的最快,下面是golfscript解决方案:

;''6666,-2%{2+.2/@*/10.3??2*+}*`1000<~;
xqkwcwgp

xqkwcwgp11#

如果您愿意使用近似值,355 / 113适用于6位小数,并且具有可用于整数表达式的附加优势。如今,这一点已经不那么重要了,因为“浮点数学协处理器”已经不再有任何意义,但它曾经相当重要。

kzipqqlq

kzipqqlq12#

使用类似机器的公式

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.

在方案中实施,例如:

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

iyfamqjs

iyfamqjs13#

圆周率正好是3![Flink教授(《辛普森一家》)]

这是个笑话,但这里有一个是用C#编写的(需要.NET框架)。

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;
        }
    }
}
6mw9ycah

6mw9ycah14#

双人套餐:

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

这将精确到小数点后14位,足以填满一个双精度(不准确可能是因为弧切线中的其余小数被截断)。

还有赛斯,它是3.141592653589793238463,不是64。

iq0todco

iq0todco15#

用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))) );

相关问题