web-dev-qa-db-ja.com

高速固定小数点pow、log、exp、sqrt

固定小数点クラス(10.22)があり、pow、sqrt、exp、およびlog関数が必要です。

残念ながら、これをどこから始めればよいのかわかりません。誰かが私に有用な記事へのリンクを提供してくれますか、それとももっと良いことに、私にいくつかのコードを提供してくれますか?

Exp関数を取得すると、powとsqrtを実装するのが比較的簡単になると思います。

pow( x, y ) => exp( y * log( x ) )
sqrt( x )   => pow( x, 0.5 )

私が難しいと感じているのは、それらのexp関数とlog関数だけです(ログルールのいくつかを覚えているかのように、それらについて他のことはあまり覚えていません)。

おそらく、sqrtとpowのより高速な方法もあるので、上記で概説した方法を使用すると言っても、その前面にあるポインターは高く評価されます。

注意:これはクロスプラットフォームであり、純粋なC/C++コードであるため、アセンブラーの最適化を使用することはできません。

21
Goz

非常に簡単な解決策は、適切なテーブル駆動型近似を使用することです。入力を正しく削減すれば、実際には多くのデータは必要ありません。 exp(a)==exp(a/2)*exp(a/2)は、実際には_1 < x < 2_に対してexp(x)を計算するだけでよいことを意味します。その範囲で、ルンゲクッタ近似は最大16エントリのIIRCで妥当な結果をもたらします。

同様に、sqrt(a) == 2 * sqrt(a/4) == sqrt(4*a) / 2は、_1 < a < 4_のテーブルエントリのみが必要であることを意味します。 Log(a)は少し難しいです:log(a) == 1 + log(a/e)。これはかなり遅い反復ですが、log(1024)は6.9しかないため、反復回数は多くありません。

Powにも同様の「整数優先」アルゴリズムを使用します:pow(x,y)==pow(x, floor(y)) * pow(x, frac(y))pow(double, int)は些細なこと(分割統治)であるため、これは機能します。

[編集] log(a)の積分コンポーネントの場合、テーブル_1, e, e^2, e^3, e^4, e^5, e^6, e^7_を格納すると、その中のaの単純なハードコードされたバイナリ検索によってlog(a) == n + log(a/e^n)を減らすことができると便利です。テーブル。 7ステップから3ステップへの改善はそれほど大きくはありませんが、neで割る代わりに、1回_e^n_で割るだけで済みます。

[編集2]そしてその最後のlog(a/e^n)用語については、log(a/e^n) = log((a/e^n)^8)/8を使用できます-各反復はさらに3ビットを生成します テーブルルックアップによる。これにより、コードとテーブルのサイズを小さく保つことができます。これは通常、組み込みシステムのコードであり、大きなキャッシュはありません。

[編集3]それは私の側では賢くないのです。 log(a) = log(2) + log(a/2)。固定小数点値_log2=0.30102999566_を格納し、先行ゼロの数をカウントし、ルックアップテーブルに使用される範囲にaをシフトし、そのシフト(整数)に固定小数点を掛けるだけです。定数_log2_。 3命令まで低くすることができます。

削減ステップにeを使用すると、「素晴らしい」log(e)=1.0定数が得られますが、これは誤った最適化です。 0.30102999566は1.0と同じくらい良い定数です。どちらも10.22固定小数点の32ビット定数です。範囲縮小の定数として2を使用すると、除算にビットシフトを使用できます。

編集2、log(a/2^n) = log((a/2^n)^8)/8からまだトリックが得られます。基本的に、これにより結果が_(a + b/8 + c/64 + d/512) * 0.30102999566_- [0,7]の範囲のb、c、dになります。 _a.bcd_は実際には8進数です。パワーとして8を使用したので、驚くことではありません。 (このトリックは、パワー2、4、または16でも同様に機能します。)

[編集4]まだオープンエンドがありました。 pow(x, frac(y)pow(sqrt(x), 2 * frac(y))であり、まともな1/sqrt(x)があります。これにより、はるかに効率的なアプローチが可能になります。 frac(y)=0.101バイナリ、つまり1/2プラス1/8と言います。つまり、_x^0.101_は_(x^1/2 * x^1/8)_です。ただし、_x^1/2_はsqrt(x)であり、_x^1/8_は_(sqrt(sqrt(sqrt(x)))_です。もう1つの操作を保存すると、Newton-Raphson NR(x)1/sqrt(x)を与えるので、1.0/(NR(x)*NR((NR(NR(x)))を計算します。最終結果を反転するだけで、sqrt関数を直接使用しないでください。

24
MSalters

以下は、Clay S. Turnerの固定小数点対数ベース2アルゴリズムのC実装の例です[ 1 ]。このアルゴリズムは、いかなる種類のルックアップテーブルも必要としません。これは、多くのマイクロコントローラの場合のように、メモリの制約が厳しく、プロセッサにFPUがないシステムで役立ちます。ログベース e 対数の基数10は、任意の基数の対数のプロパティを使用してサポートされます。 n

          logₘ(x)
logₙ(x) = ───────
          logₘ(n)

ここで、このアルゴリズムの場合、 m 2に等しい。

この実装の優れた機能は、可変精度をサポートしていることです。精度は、範囲を犠牲にして実行時に決定できます。私が実装した方法では、プロセッサ(またはコンパイラ)は、いくつかの中間結果を保持するために64ビットの計算を実行できる必要があります。 64ビットのサポートを必要としないように簡単に適合させることができますが、範囲は狭くなります。

これらの関数を使用する場合、xは、指定されたprecisionに従ってスケーリングされた固定小数点値であることが期待されます。たとえば、precisionが16の場合、xは2 ^ 16(65536)でスケーリングする必要があります。結果は、入力と同じスケール係数の固定小数点値です。 INT32_MINの戻り値は、負の無限大を表します。戻り値INT32_MAXはエラーを示し、errnoEINVALに設定され、入力精度が無効であることを示します。

#include <errno.h>
#include <stddef.h>

#include "log2fix.h"

#define INV_LOG2_E_Q1DOT31  UINT64_C(0x58b90bfc) // Inverse log base 2 of e
#define INV_LOG2_10_Q1DOT31 UINT64_C(0x268826a1) // Inverse log base 2 of 10

int32_t log2fix (uint32_t x, size_t precision)
{
    int32_t b = 1U << (precision - 1);
    int32_t y = 0;

    if (precision < 1 || precision > 31) {
        errno = EINVAL;
        return INT32_MAX; // indicates an error
    }

    if (x == 0) {
        return INT32_MIN; // represents negative infinity
    }

    while (x < 1U << precision) {
        x <<= 1;
        y -= 1U << precision;
    }

    while (x >= 2U << precision) {
        x >>= 1;
        y += 1U << precision;
    }

    uint64_t z = x;

    for (size_t i = 0; i < precision; i++) {
        z = z * z >> precision;
        if (z >= 2U << (uint64_t)precision) {
            z >>= 1;
            y += b;
        }
        b >>= 1;
    }

    return y;
}

int32_t logfix (uint32_t x, size_t precision)
{
    uint64_t t;

    t = log2fix(x, precision) * INV_LOG2_E_Q1DOT31;

    return t >> 31;
}

int32_t log10fix (uint32_t x, size_t precision)
{
    uint64_t t;

    t = log2fix(x, precision) * INV_LOG2_10_Q1DOT31;

    return t >> 31;
}

この実装のコードも Github にあり、この関数を使用して標準入力から読み取った数値から対数を計算して表示する方法を示すサンプル/テストプログラムがあります。

[ 1 ] C. S.ターナー、 "高速2進対数アルゴリズム" 、 IEEE信号処理マグ.、pp。124,140、2010年9月。

9
Dan Moulding

良い出発点は Jack Crenshawの本、 "Math Toolkit for Real-Time Programming" です。さまざまな超越関数のアルゴリズムと実装について十分に説明しています。

5
Paul R

整数演算のみを使用して、固定小数点sqrtの実装を確認してください。発明するのは楽しかったです。今はかなり古い。

https://groups.google.com/forum/?hl=fr%05aacf5997b615c37&fromgroups#!topic/comp.lang.c/IpwKbw0MAxw/discussion

それ以外の場合は、 [〜#〜] cordic [〜#〜] アルゴリズムのセットを確認してください。これが、リストしたすべての関数と三角関数を実装する方法です。

編集:レビュー済みのソースをGitHubに公開しました ここ

3
chmike