古い質問ですが、それでも人々はまだ興味を持っているかもしれません。
したがって、Kevin はテイラー多項式で正しいアイデアを得ましたが、そこからアルゴリズムを直接導出すると、問題が発生する可能性があります。主に、$i に大きなカットオフ値を使用すると、長い入力文字列に対してコードが遅くなります。
理由は次のとおりです。すべてのステップで、つまり新しい $i ごとに、コードは bcfac($i) を呼び出します。bcfac が呼び出されるたびに、$i-1 の計算が実行されます。$i は 299 まで続きます...これはほぼ 45000 回の操作です! 迅速で簡単な浮動小数点演算ではなく、BC 文字列演算が遅い - bcscale(100) を設定すると、bcmul は最大 10000 ペアの文字を処理する必要があります。
また、bcpow も $i を増やすと遅くなります。bcfac ほどではありません。これは、2 乗法に似たものをおそらく使用するためですが、それでも何かが追加されます。
全体として、必要な時間は、計算される多項式の項の数に応じて 2 次的に増加します。
じゃあ何をすればいいの?
ヒントは次のとおりです。
多項式、特にテイラー多項式を扱うときはいつでも、ホーナー法を使用してください。
これを変換します: exp(x) = x^0/0! + x^1/1! + x^2/2! + x^3/3! + ...
...それに: exp(x) = ((( ... )*x/3+1 )*x/2+1 )*x/1+1
そして突然、累乗や階乗がまったく必要なくなりました!
function bc_exp($number) {
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $number), 1);
return $result;
}
これは、$i が何であっても、各ステップで 3 つの bc 操作しか必要としません。$i=299 の開始値 (kevin のコードと同じ精度で exp を計算するため) では、必要な bc 操作は 45000 以上であるのに対し、897 回しか必要ありません。カットオフとして 300 ではなく 30 を使用しても、今では87 の bc 操作だけが必要ですが、他のコードでは階乗だけで 822 が必要です。
ホーナーのメソッドがまたもや救ってくれました!
その他の考え:
1) Kevin のコードは、最初のステップ ($i=0) で bcpow(0,0) を試行するため、bcmath がエラーを処理する方法によっては、input="0" でクラッシュする可能性があります。
2) 指数が大きいほど多項式が長くなるため、より多くの反復が必要になります。たとえば、bc_exp(300) は $i=299 であっても間違った答えを返します。各項は x^n/n を追加します! そのため、多項式が収束し始める前に、この項を小さくする必要があります。次に、連続する 2 つの項を比較します。
( x^(n+1)/(n+1)! ) / ( x^n/n! ) = x/n
各被加数は x/n (Horner 法で使用) 倍前のものよりも大きいため、x^(n+1)/(n+1) の順序で! 小さくするには、x/n も小さくする必要があります。これは、n>x の場合のみです。
結論: 反復回数が入力値よりも小さい限り、結果は発散します。反復回数が入力よりも大きくなるまでステップを追加する場合にのみ、アルゴリズムはゆっくりと収束し始めます。
bcmath を使用する意思がある人を満足させる結果に到達するには、$i を $number よりも大幅に大きくする必要があります。e^346674567801 のようなものを計算しようとすると、これは大きな問題です。
解決策は、入力を整数部分と小数部分に分割することです。整数部分に bcpow を使用し、小数部分に bc_exp を使用すると、小数部分が 1 より小さいため、最初から収束します。最後に、結果を乗算します。
e^x = e^(intpart+fracpart) = e^intpart * e^fracpart = bcpow(e,intpart) * bc_exp(fracpart)
上記のコードに直接実装することもできます。
function bc_exp2($number) {
$parts = explode (".", $number);
$fracpart = "0.".$parts[1];
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $fracpart), 1);
$result = bcmul(bcpow(exp(1), $parts[0]), $result);
return $result;
}
exp(1) は、おそらく bcmath ユーザーとしてのニーズを満たさない浮動小数点数を与えることに注意してください。bcscale の設定に従って、より正確な e の値を使用することをお勧めします。
3) 反復回数について: 300 はほとんどの状況でやり過ぎですが、他の状況では十分でない場合もあります。あなたの bcscale と $number を取り、必要な反復回数を計算するアルゴリズムはいいでしょう。log(n!) に関するいくつかのアイデアは既に得られていますが、具体的なものはまだありません。
4) このメソッドを任意の基数で使用するには、a^x = e^(x*ln(a)) を使用できます。不必要な関数呼び出しを避けるために、bc_exp を使用する前に (bc_exp2 内で行うのではなく) x を intpart と fracpart に分割することをお勧めします。
function bc_pow2($base,$exponent) {
$parts = explode (".", $exponent);
if ($parts[1] == 0){
$result = bcpow($base,$parts[0]);
else $result = bcmul(bc_exp(bcmul(bc_ln($base), "0.".$parts[1]), bcpow($base,$parts[0]);
return result;
}
これで、bc_ln をプログラムするだけで済みます。上記と同じ戦略を使用できます。
自然対数関数のテイラー多項式をとります。(ln(0) が定義されていないため、代わりに 1 を開発ポイントとして使用します) ホーナーの方法を使用して、パフォーマンスを大幅に改善します。結果を bc 操作のループに変換します。また、収束を保証するために、x > 1 を処理する場合は ln(x) = -ln(1/x) を使用します。