web-dev-qa-db-ja.com

数学定数eを計算する効率的な方法

無限級数の合計としての定数eの標準表現は、多くの除算演算があるため、計算には非常に非効率的です。では、定数を効率的に計算する別の方法はありますか?

ありがとう!

[〜#〜]編集[〜#〜]

あなたのリンクのいくつかをたどった後、効率は私がよく知らなかったバイナリ分割と呼ばれる技術から来ると思います(表現はまだシリーズに言及されていますが)。誰かがそれに精通しているなら、気軽に貢献してください。

29
Cinnamon

'e'のすべての桁を計算することはできないため、停止点を選択する必要があります。

倍精度:小数点以下16桁

実際のアプリケーションでは、「「e」の真の値に可能な限り近い64ビットの倍精度浮動小数点値-約16桁の10進数」で十分です。

KennyTMが言ったように、その値はすでに数学ライブラリで事前に計算されています。ハンス・パッサントが指摘したように、自分で計算したい場合、階乗はすでに非常に速く成長します。シリーズの最初の22項は、その精度で計算するにはすでにやり過ぎです。シリーズからさらに項を追加しても、64ビットの倍精度浮動小数点変数に格納されている場合は結果が変わりません。コンピューターが22分割するよりも、まばたきに時間がかかると思います。したがって、これをさらに最適化する理由はわかりません。

数千、数百万、または数十億の10進数

Matthieu M.が指摘したように、この値はすでに計算されており、YeeのWebサイトからダウンロードできます。

自分で計算したい場合、その数の桁は標準の倍精度浮動小数点数に収まりません。 「bignum」ライブラリが必要です。いつものように、すでに利用可能な多くの無料のbignumライブラリのいずれかを使用するか、独自の特殊な癖を備えた独自のさらに別のbignumライブラリを構築して車輪の再発明を行うことができます。

結果(数字の長いファイル)はそれほど有用ではありませんが、それを計算するプログラムは、「bignum」ライブラリソフトウェアのパフォーマンスと精度をテストするためのベンチマークとして、および安定性と冷却能力をチェックするためのストレステストとして使用されることがあります。新しいマシンハードウェアの。

1ページで非常に簡単に説明されています Yeeが数学定数を計算するために使用するアルゴリズム

ウィキペディア 「バイナリ分割」の記事 さらに詳しく説明します。あなたが探しているのは数値表現だと思います。すべての数値を小数点(または2進数)の前後の長い一連の数字として内部的に格納する代わりに、Yeeは各項と各部分和を有理数として格納します--2つの整数として、それぞれが長い一連の数字です。たとえば、ワーカーCPUの1つに部分和が割り当てられたとします。

... 1/4! + 1/5! + 1/6! + ... .

最初に各項に対して除算を実行し、次に加算してから、100万桁の固定小数点の結果をマネージャーCPUに返す代わりに、次のようにします。

// extended to a million digits
1/24 + 1/120 + 1/720 => 0.0416666 + 0.0083333 + 0.00138888

そのCPUは、最初に有理算術で級数のすべての項を加算し、有理数の結果をマネージャーCPUに返すことができます。それぞれがおそらく数百桁の2つの整数です。

// faster
1/24 + 1/120 + 1/720 => 1/24 + 840/86400 => 106560/2073600

この方法で数千の用語が追加された後、マネージャーCPUは最後に唯一の除算を実行して、小数点以下の小数点以下の桁を取得します。

PrematureOptimization を避け、常に ProfileBeforeOptimizing を避けることを忘れないでください。

24
David Cary

級数のテイラー展開よりも「速い」計算を私は知りません。

e = 1/0! + 1/1! + 1/2! +.。

または

1/e = 1/0! -1/1! + 1/2! -1/3! +.。

これらがA.Yeeによって使用されたことを考えると、 e の最初の5000億桁を計算したので、最適化はあまり行われていないと思います(またはより良いことに、それは最適化される可能性がありますが、まだ誰も方法を見つけていません、AFAIK)

[〜#〜]編集[〜#〜]

非常に大まかな実装

#include <iostream>
#include <iomanip>

using namespace std;

double gete(int nsteps)
{
  // Let's skip the first two terms
  double res = 2.0;
  double fact = 1;

  for (int i=2; i<nsteps; i++)
  {
    fact *= i;
    res += 1/fact;
  }

  return res;
}

int main()
{
  cout << setprecision(50) << gete(10) << endl;
  cout << setprecision(50) << gete(50) << endl;
}

出力

2.71828152557319224769116772222332656383514404296875
2.71828182845904553488480814849026501178741455078125
10
nico

doubleまたはfloatを使用している場合は、M_E定数math.hすでに。

#define M_E         2.71828182845904523536028747135266250   /* e */

http://en.wikipedia.org/wiki/Representations_of_e#As_an_infinite_series ;にはeの他の表現があります。それらはすべて分割を伴います。

10
kennytm

このページ さまざまな計算方法の概要があります。

これは、Xavier Gourdonの小さなCプログラムで、コンピューターでeの小数点以下9000桁を計算します。同じ種類のプログラムが、πおよび超幾何級数によって定義された他のいくつかの定数に対して存在します。

[deglolfed version from https://codereview.stackexchange.com/a/33019 ]

#include <stdio.h>
int main() {
      int N = 9009, a[9009], x;
      for (int n = N - 1; n > 0; --n) {
          a[n] = 1;
      }
      a[1] = 2;
      while (N > 9) {
          int n = N--;
          while (--n) {
              a[n] = x % n;
              x = 10 * a[n-1] + x/n;
          }
          printf("%d", x);
      }
      return 0;
  }

このプログラム[コードゴルフの場合]は117文字です。より多くの桁を計算するように(値9009をより多くに変更する)、より速くするように変更できます(定数10を10の累乗とprintfコマンドに変更します)。それほど明白ではない質問は、使用されているアルゴリズムを見つけることです。

8
tzaman

私はCodeReviewsで テイラー級数による定義によるeの計算に関する質問 (したがって、他の方法はオプションではありませんでした)でこの答えを出しました。ここのクロスポストはコメントで提案されました。他のトピックに関連するコメントを削除しました。 migthの詳細な説明に興味のある方は、元の投稿を確認してください。


Cのソリューション(C++に適応するのに十分簡単である必要があります):

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

int main ()
{
    long double n = 0, f = 1;
    int i;
    for (i = 28; i >= 1; i--) {
        f *= i;  // f = 28*27*...*i = 28! / (i-1)!
        n += f;  // n = 28 + 28*27 + ... + 28! / (i-1)!
    }  // n = 28! * (1/0! + 1/1! + ... + 1/28!), f = 28!
    n /= f;
    printf("%.64llf\n", n);
    printf("%.64llf\n", expl(1));
    printf("%llg\n", n - expl(1));
    printf("%d\n", n == expl(1));
}

出力:

2.7182818284590452354281681079939403389289509505033493041992187500
2.7182818284590452354281681079939403389289509505033493041992187500
0
1

2つの重要なポイントがあります:

  1. このコードは、O(n ^ 2)である1、1 * 2、1 * 2 * 3、...を計算しませんが、1回のパス(O(n)で1 * 2 * 3 * ...を計算します。 ))。

  2. それは小さな数字から始まります。計算しようとした場合

    1/1 + 1/2 + 1/6 + ... + 1/20!

    1/21を追加しようとしました!、追加します

    1/21! = 1/51090942171709440000 = 2E-20、

    結果に影響を与えない2.somethingへ(doubleは約16桁の有効数字を保持します)。この効果は アンダーフロー と呼ばれます。

    ただし、これらの数値から始める場合、つまり1/32!+1/31!+ ...を計算すると、すべて何らかの影響があります。

このソリューションは、gcc 4.7.2 20120921でコンパイルされた64ビットマシンで、Cがexpl関数を使用して計算するものに準拠しているようです。

6
Vedran Šego

あなたはある程度の効率を得ることができるかもしれません。各項にはnext階乗が含まれるため、階乗の最後の値を記憶することである程度の効率が得られる場合があります。

e = 1 + 1/1! + 1/2! + 1/3! ...  

方程式を拡張する:

e = 1 + 1/(1 * 1) + 1/(1 * 1 * 2) + 1/(1 * 2 * 3) ...

各階乗を計算する代わりに、分母に次の増分を掛けます。したがって、分母を変数として保持し、それを乗算すると、ある程度の最適化が得られます。

3
Thomas Matthews

無制限の方法で数字を順番に計算するいくつかの「スピゴット」アルゴリズムがあります。これは、生成する桁数を事前に定義しなくても、一定数の基本的な算術演算によって「次の」桁を簡単に計算できるため便利です。

これらは、次の桁が1の位置に来るように一連の連続した変換を適用するため、浮動小数点の丸め誤差の影響を受けません。これらの変換は、整数の加算と乗算に還元される行列の乗算として定式化できるため、効率が高くなります。

要するに、テイラー級数展開

e = 1/0! + 1/1! + 1/2! + 1/3! ... + 1/n!

階乗の小数部分を因数分解することで書き直すことができます(シリーズを規則的にするために、1を左側に移動したことに注意してください):

(e - 1) = 1 + (1/2)*(1 + (1/3)*(1 + (1/4)...))

一連の関数f1(x)... fn(x)を次のように定義できます。

f1(x) = 1 + (1/2)x
f2(x) = 1 + (1/3)x
f3(x) = 1 + (1/4)x
...

Eの値は、これらすべての関数の構成から求められます。

(e-1) = f1(f2(f3(...fn(x))))

各関数のxの値は次の関数によって決定され、これらの値のそれぞれは範囲[1,2]に制限されていることがわかります。つまり、これらの関数のいずれについても、xの値は次のようになります。 1 <= x <= 2

これが当てはまるので、xにそれぞれ値1と2を使用して、eの下限と上限を設定できます。

lower(e-1) = f1(1) = 1 + (1/2)*1 = 3/2 = 1.5
upper(e-1) = f1(2) = 1 + (1/2)*2 = 2

上で定義した関数を作成することで精度を上げることができます。桁が下限と上限で一致すると、計算されたeの値がその桁に正確であることがわかります。

lower(e-1) = f1(f2(f3(1))) = 1 + (1/2)*(1 + (1/3)*(1 + (1/4)*1)) = 41/24 = 1.708333
upper(e-1) = f1(f2(f3(2))) = 1 + (1/2)*(1 + (1/3)*(1 + (1/4)*2)) = 7/4 = 1.75

1と10の桁が一致するため、10分の1の精度での(e-1)の近似は1.7であると言えます。最初の桁が上限と下限の間で一致する場合、それを減算してから10を掛けます。これにより、問題の桁は常に浮動小数点の精度が高い1の位置になります。

実際の最適化は、線形関数を変換行列として記述する線形代数の手法に由来します。合成関数は行列の乗算にマップされるため、これらの入れ子関数はすべて、単純な整数の乗算と加算に減らすことができます。桁を減算して10を乗算する手順も線形変換を構成するため、行列の乗算によっても実行できます。

メソッドの別の説明: http://www.hulver.com/scoop/story/2004/7/22/153549/352

アルゴリズムを説明する論文: http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/spigot.pdf

行列演算を介して線形変換を実行するための簡単な紹介: https://people.math.gatech.edu/~cain/notes/cal6.pdf

注意:このアルゴリズムは、ギボンズの論文で簡単に説明されている線形変換の一種であるメビウス変換を利用しています。

2
aaaarrgh

最大7桁の概算で問題がない場合は、

3-sqrt(5/63)
2.7182819

正確な値が必要な場合:

e = (-1)^(1/(j*pi))

ここで、jは虚数単位、piはよく知られている数学定数(オイラーの等式)です。

2
George Dontas

バイナリ分割方法は、eの近似に対応する有理数を表す型を生成するテンプレートメタプログラムに適しています。 13回の反復が最大のようです。これを超えると、「積分定数オーバーフロー」エラーが発生します。

_#include <iostream>
#include <iomanip>

template<int NUMER = 0, int DENOM = 1>
struct Rational
{
    enum {NUMERATOR = NUMER};
    enum {DENOMINATOR = DENOM};

    static double value;
};

template<int NUMER, int DENOM>
double Rational<NUMER, DENOM>::value = static_cast<double> (NUMER) / DENOM;

template<int ITERS, class APPROX = Rational<2, 1>, int I = 2>
struct CalcE
{
    typedef Rational<APPROX::NUMERATOR * I + 1, APPROX::DENOMINATOR * I> NewApprox;
    typedef typename CalcE<ITERS, NewApprox, I + 1>::Result Result;
};

template<int ITERS, class APPROX>
struct CalcE<ITERS, APPROX, ITERS>
{
    typedef APPROX Result;
};

int test (int argc, char* argv[])
{
    std::cout << std::setprecision (9);

    // ExpType is the type containing our approximation to e.
    typedef CalcE<13>::Result ExpType;

    // Call result() to produce the double value.
    std::cout << "e ~ " << ExpType::value << std::endl;

    return 0;
}
_

別の(メタプログラムではない)テンプレート化されたバリエーションは、コンパイル時に、二重近似eを計算します。これには反復回数の制限はありません。

_#include <iostream>
#include <iomanip>

template<int ITERS, long long NUMERATOR = 2, long long DENOMINATOR = 1, int I = 2>
struct CalcE
{
    static double result ()
    {
        return CalcE<ITERS, NUMERATOR * I + 1, DENOMINATOR * I, I + 1>::result ();
    }
};

template<int ITERS, long long NUMERATOR, long long DENOMINATOR>
struct CalcE<ITERS, NUMERATOR, DENOMINATOR, ITERS>
{
    static double result ()
    {
        return (double)NUMERATOR / DENOMINATOR;
    }
};

int main (int argc, char* argv[])
{
    std::cout << std::setprecision (16);

    std::cout << "e ~ " <<  CalcE<16>::result () << std::endl;

    return 0;
}
_

最適化されたビルドでは、式CalcE<16>::result ()は実際のdouble値に置き換えられます。

どちらもコンパイル時にeを計算するため、ほぼ間違いなく非常に効率的です:-)

1
jon-hanson

私の見解では、eを目的の精度まで計算する最も効率的な方法は、次の表現を使用することです。

e := lim (n -> inf): (1 + (1/n))^n

特に_n = 2^x_を選択した場合、次の理由から、x回の乗算だけで効力を計算できます。

a^n = (a^2)^(n/2), if n % 2 = 0

1
Dave O.

@nico Re:

...級数のテイラー展開よりも「速い」計算、つまり:

e = 1/0! + 1/1! + 1/2! +.。

または

1/e = 1/0! -1/1! + 1/2! -1/3! +.。

ニュートン法の収束を代数的に改善する方法は次のとおりです。

https://www.researchgate.net/publication/52005980_Improving_the_Convergence_of_Newton 's_Series_Approximation_for_e

バイナリ分割と組み合わせて使用​​して計算速度を上げることができるかどうかについては、未解決の問題のようです。それでも、Perlを使用したDamian Conwayの例は、この新しいアプローチの直接計算効率の向上を示しています。 「????」というタイトルのセクションにあります見積もり用です」:

http://blogs.Perl.org/users/damian_conway/2019/09/to-compute-a-constant-of-calculusa-treatise-on-multiple-ways.html

(このコメントは長すぎて、2010年6月12日の10:28に回答の返信として投稿できません)

0
Harlan