少し前に、ライブラリ関数を使用せずにを計算しようとするコードを書きました。昨日、私は古いコードをレビューしていて、それを可能な限り速くするように試みました(そして正しい)。これまでの私の試みは次のとおりです。
const double ee = exp(1);
double series_ln_taylor(double n){ /* n = e^a * b, where a is an non-negative integer */
double lgVal = 0, term, now;
int i, flag = 1;
if ( n <= 0 ) return 1e-300;
if ( n * ee < 1 )
n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */
for ( term = 1; term < n ; term *= ee, lgVal++ );
n /= term;
/* log(1 - x) = -x - x**2/2 - x**3/3... */
n = 1 - n;
now = term = n;
for ( i = 1 ; ; ){
lgVal -= now;
term *= n;
now = term / ++i;
if ( now < 1e-17 ) break;
}
if ( flag == -1 ) lgVal = -lgVal;
return lgVal;
}
ここでは、e aがちょうどnを超えるようにを見つけようとしています。次に、nの対数値を追加します。は1未満です。この時点で、log(1−x)のテイラー展開は心配することなく使用できます。
最近、数値分析への関心が高まりました。そのため、このコードセグメントを実際にどれだけ速く実行できるか、十分に正確であるかという質問をしざるを得ません。このような継続分数を使用するなど、他のいくつかの方法に切り替える必要がありますか?
C標準ライブラリで提供される機能は、ほぼ5.1倍高速この実装よりなります。
更新1:Wikipediaで言及されている双曲線arctanシリーズを使用すると、計算はC標準ライブラリのログ関数よりも約2.2倍遅いようです。しかし、私は広範囲に渡ってパフォーマンスをチェックしていませんし、より大きな数の場合、私の現在の実装は本当に遅いようです。管理できる場合は、実装の両方でエラーの範囲と幅広い数値の平均時間を確認したいと思います。これが私の2番目の取り組みです。
double series_ln_arctanh(double n){ /* n = e^a * b, where a is an non-negative integer */
double lgVal = 0, term, now, sm;
int i, flag = 1;
if ( n <= 0 ) return 1e-300;
if ( n * ee < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */
for ( term = 1; term < n ; term *= ee, lgVal++ );
n /= term;
/* log(x) = 2 arctanh((x-1)/(x+1)) */
n = (1 - n)/(n + 1);
now = term = n;
n *= n;
sm = 0;
for ( i = 3 ; ; i += 2 ){
sm += now;
term *= n;
now = term / i;
if ( now < 1e-17 ) break;
}
lgVal -= 2*sm;
if ( flag == -1 ) lgVal = -lgVal;
return lgVal;
}
提案や批判はありがたいです。
更新2:以下の提案に基づいて、標準のライブラリ実装よりも約2.5倍遅い、いくつかの増分変更をここに追加しました。しかし、私は整数のみのためにそれをテストしているランタイムが増加するであろう多数のため、この時間を、。今のところ。私はまだ、ランダムな二重の数字を生成する技術を知らない≤ 1 E 308をので、それはまだ完全にはベンチマークはありません。コードをより堅牢にするために、コーナーケースの修正を追加しました。私が行ったテストの平均誤差は約4 e − 15です。
double series_ln_better(double n){ /* n = e^a * b, where a is an non-negative integer */
double lgVal = 0, term, now, sm;
int i, flag = 1;
if ( n == 0 ) return -1./0.; /* -inf */
if ( n < 0 ) return 0./0.; /* NaN*/
if ( n < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */
/* the cutoff iteration is 650, as over e**650, term multiplication would
overflow. For larger numbers, the loop dominates the arctanh approximation
loop (with having 13-15 iterations on average for tested numbers so far */
for ( term = 1; term < n && lgVal < 650 ; term *= ee, lgVal++ );
if ( lgVal == 650 ){
n /= term;
for ( term = 1 ; term < n ; term *= ee, lgVal++ );
}
n /= term;
/* log(x) = 2 arctanh((x-1)/(x+1)) */
n = (1 - n)/(n + 1);
now = term = n;
n *= n;
sm = 0;
/* limiting the iteration for worst case scenario, maximum 24 iteration */
for ( i = 3 ; i < 50 ; i += 2 ){
sm += now;
term *= n;
now = term / i;
if ( now < 1e-17 ) break;
}
lgVal -= 2*sm;
if ( flag == -1 ) lgVal = -lgVal;
return lgVal;
}