[performance] π의 가치를 얻는 가장 빠른 방법은 무엇입니까?

개인적 도전으로 π의 가치를 얻는 가장 빠른 방법을 찾고 있습니다. 보다 구체적으로, 나는 #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.cx86 및 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)버전은 항상 맨 위에 나옵니다.



답변

몬테카를로 방법은 없습니다 합리적인 조치가 아닌 빠른 아니라 장거리 슛으로, 명확하게, 언급 한 바와 같이, 훌륭한 개념을 적용하지만입니다. 또한, 그것은 당신이 찾고있는 정확성의 종류에 달려 있습니다. 내가 아는 가장 빠른 π는 숫자가 하드 코딩 된 것입니다. 보면 파이파이 [PDF] , 수식이 많이 있습니다.

반복 당 약 14 자리 숫자로 빠르게 수렴하는 방법이 있습니다. 현재 가장 빠른 응용 프로그램 인 PiFast 는이 수식을 FFT와 함께 사용합니다. 코드가 간단하기 때문에 수식을 작성하겠습니다. 이 공식은 거의 Ramanujan에 의해 발견되었고 Chudnovsky에 의해 발견되었습니다 . 실제로 그는 수십억 자리의 숫자를 계산 한 방식이므로 무시할 방법이 아닙니다. 수식은 빠르게 오버플로되며 계승을 나누기 때문에 이러한 계산을 지연시켜 항을 제거하는 것이 유리합니다.

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

어디,

여기에 이미지 설명을 입력하십시오

아래는 브렌트-살라 민 알고리즘 입니다. Wikipedia는 ab 가 “충분히 가까이”있을 때 (a + b) ² / 4t 는 π의 근사치 라고 언급합니다 . 나는 “충분히 근접하다”는 것이 무엇인지 확실하지 않지만 테스트에서 하나의 반복은 2 자리, 2는 7, 3은 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);}


답변

나는이 프로그램을 좋아합니다. 왜냐하면 자체 영역을 보면서 π와 비슷하기 때문입니다.

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()
{
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
 _-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
        _-_-_-_-_-_-_-_
            _-_-_-_
}

답변

다음은 고등학교에서 배운 파이를 계산하는 기술에 대한 일반적인 설명입니다.

나는 누군가가 그것을 영원히 기억할 수있을 정도로 단순하다고 생각하기 때문에 이것을 공유한다. 또한 “Monte-Carlo”방법의 개념을 가르쳐 준다. 임의의 프로세스를 통해 공제 가능합니다.

정사각형을 그리고 그 정사각형 내부에 사분면 (반원의 1/4)을 쓰십시오 (사각의 측면과 같은 반경을 갖는 사분면은 가능한 많은 정사각형을 채 웁니다)

이제 사각형에 다트를 던져서 그 위치를 기록하십시오. 즉, 사각형 내부의 임의의 지점을 선택하십시오. 물론 그것은 사각형 안에 들어 갔지만 반원 안에 있습니까? 이 사실을 기록하십시오.

이 과정을 여러 번 반복하십시오. 반원 내부의 포인트 수 대 던진 총 수의 비율이 있음을 알 수 있습니다.이 비율을 x라고하십시오.

정사각형의 면적이 r 곱하기 r이므로, 반원의 면적이 x 곱하기 r 곱하기 r (즉, x 곱하기 r 제곱)이라고 추론 할 수 있습니다. 따라서 x 곱하기 4는 파이를 줄 것입니다.

이것은 빠른 방법이 아닙니다. 그러나 이것은 몬테 카를로 방법의 좋은 예입니다. 주변을 둘러 보면 계산 기술 이외의 많은 문제가 그러한 방법으로 해결 될 수 있습니다.


답변

완전성을 위해 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 번의 반복을 위해 (메모 화가없는 경우) value ()에 대한 약 80k 호출이 있다고 생각합니다.


답변

Jonathan과 Peter Borwein ( Amazon에서 사용 가능 ) 의 \ pi 계산 : ‘Pi and AGM’에 대한 빠른 방법을 다룬 책이 실제로 있습니다 .

나는 AGM과 관련 알고리즘을 꽤 많이 연구했다. 그것은 꽤 흥미 롭다.

\ pi를 계산하기 위해 대부분의 최신 알고리즘을 구현하려면 다중 정밀도 산술 라이브러리가 필요합니다 ( 마지막으로 사용한 지 오래되었지만 GMP 는 좋은 선택입니다).

최상의 알고리즘의 시간 복잡도는 O (M (n) log (n))에 있으며, 여기서 M (n)은 두 n- 비트 정수의 곱셈에 대한 시간 복잡도입니다 (M (n) = O (n FFT 기반 알고리즘을 사용하여 log (n) log (log (n)))). 이는 일반적으로 \ pi의 숫자를 계산할 때 필요하며 이러한 알고리즘은 GMP로 구현됩니다.

알고리즘 뒤의 수학이 사소한 것은 아니지만 알고리즘 자체는 일반적으로 몇 줄의 의사 코드이며, 구현은 일반적으로 매우 간단합니다 (자신의 다중 정밀도 산술을 쓰지 않기로 선택한 경우 :-)).


답변

다음 은 최소한의 컴퓨팅 노력으로 가능한 가장 빠른 방법으로이를 수행하는 방법에 대한 정확한 답변 입니다. 대답이 마음에 들지 않더라도 실제로 PI의 가치를 얻는 가장 빠른 방법임을 인정해야합니다.

가장 빠른 파이의 값을 얻을 수있는 방법입니다 :

1) 좋아하는 프로그래밍 언어를 선택했습니다. 2) 수학 라이브러리를로드합니다. 3) Pi가 이미 정의되어 있습니다.

현재 수학 라이브러리가없는 경우 ..

번째로 빠른 방법 (보다 보편적 인 솔루션)은 다음과 같습니다.

인터넷에서 Pi를 찾으십시오 (예 : 여기).

http://www.eveandersson.com/pi/digits/1000000 (100 만 자리 .. 부동 소수점 정밀도는 무엇입니까?)

또는 여기 :

http://3.141592653589793238462643383279502884197169399375105820974944592.com/

또는 여기 :

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

사용하려는 정밀 산술에 필요한 숫자를 찾는 것이 정말 빠르며 상수를 정의하여 소중한 CPU 시간을 낭비하지 않도록 할 수 있습니다.

이것은 부분적으로 유머러스 한 답변 일뿐만 아니라, 실제로 누군가가 실제 응용 프로그램에서 Pi의 가치를 계산하고 계산할 경우 CPU 시간을 상당히 낭비하는 것입니까? 적어도 이것을 다시 계산하려고하는 실제 응용 프로그램은 보이지 않습니다.

친애하는 중재자 : OP가 다음과 같이 질문했습니다. “PI의 가치를 얻는 가장 빠른 방법”


답변

BBP 수식은 베이스 (2) (16)에서 – -는 n 번째 자릿수를 계산할 수 있도록 제 심지어 이전 N-1 자리 귀찮게하지 않고 🙂