web-dev-qa-db-ja.com

C ++でのサイン、コサイン、平方根の最速実装(それほど正確である必要はありません)

私は過去1時間にわたって質問をグーグルで調べていますが、テイラーシリーズまたはいくつかのサンプルコードが遅すぎるか、まったくコンパイルされていないという点があります。さて、Googleで見つけたほとんどの答えは「Google it、それはすでに尋ねられています」ですが、悲しいことにではありません...

ローエンドのPentium 4でゲームをプロファイリングしていますが、実行時間の〜85%が洞、余弦、平方根(Visual Studioの標準C++ライブラリから)の計算に浪費され、これはCPUに大きく依存しているようです(私のI7では、同じ関数は実行時間の5%しか得られず、ゲームはwaaaaaaaaaayより高速です)。この3つの関数を最適化することも、1パスでサインとコサインの両方を計算することもできません(相互依存性があります)が、シミュレーションにあまり正確な結果を必要としないため、より高速な近似で生きることができます。

それで、質問:C++の浮動小数点の正弦、余弦、平方根を計算する最も速い方法は何ですか?

[〜#〜] edit [〜#〜]ルックアップテーブルは、結果のキャッシュミスが最新のCPUではテイラーシリーズよりもコストが高いため、より苦痛です。最近のCPUは非常に高速ですが、キャッシュはそうではありません。

ミスを犯しましたが、テイラー級数のいくつかの階乗を計算する必要があり、定数として実装できるようになりました。

更新された質問:平方根の高速な最適化もありますか?

EDIT2

私は正規化ではなく平方根を使用して距離を計算しています-高速逆平方根アルゴリズムを使用できません(コメントで指摘されているように: http://en.wikipedia.org/wiki/Fast_inverse_square_root

EDIT3

また、二乗距離では操作できません。計算には正確な距離が必要です

33
PiotrK

最速の方法は、値を事前計算し、次の例のようなテーブルを使用することです。

C++でサインルックアップテーブルを作成

しかし、実行時にコンピューティングを主張する場合は、サインまたはコサインのテイラー級数展開を使用できます...

Taylor Series of sine

テイラーシリーズの詳細については... http://en.wikipedia.org/wiki/Taylor_series

これをうまく機能させるための鍵の1つは、階乗を事前計算し、適切な数の用語で切り捨てることです。階乗は分母で非常に急速に成長するため、数項以上を運ぶ必要はありません。

また...毎回開始からx ^ nを掛けないでください... x ^ 3にxをさらに2回掛けてから、さらに2を掛けて指数を計算します。

17
KeatsKelleher

第一に、テイラー級数は、サイン/コサインを実装する最良/最速の方法ではありません。また、プロのライブラリがこれらの三角関数を実装する方法ではありません。最適な数値実装を知っていると、精度を調整して速度をより効率的に上げることができます。さらに、この問題はStackOverflowで既に広範囲に議論されています。 これはほんの一例です

第二に、古いPCSと新しいPCSの大きな違いは、最新のIntelアーキテクチャには、基本的な三角関数を計算するための明示的なアセンブリコードがあるためです。実行速度でそれらを打ち負かすことは非常に困難です。

最後に、古いPCのコードについて話しましょう。 gsl gnu科学ライブラリ (または数値レシピ)の実装を確認すると、基本的にチェビシェフ近似式が使用されていることがわかります。

チェビシェフ近似はより速く収束するため、より少ない項を評価する必要があります。 StackOverflowには既に非常に良い回答が投稿されているため、ここでは実装の詳細を書きません。 たとえば、これをチェックしてください このシリーズの用語の数を微調整して、精度/速度のバランスを変更します。

この種の問題について、何らかの特別な関数や数値メソッドの実装の詳細が必要な場合は、さらにアクションを実行する前にGSLコードを確認する必要があります。GSLは標準数値ライブラリです。

編集:gcc/iccに積極的な浮動小数点最適化フラグを含めることにより、実行時間を改善できます。これは精度を低下させますが、それはまさにあなたが望むもののようです。

EDIT2:粗いsinグリッドを作成し、gslルーチン(周期的条件のスプラインのgsl_interp_cspline_periodic)を使用してそのテーブルをスプラインしようとすることができます(スプラインは線形補間と比較してエラーを削減します=>テーブル上のポイントが少ない= >キャッシュミスの減少)!

40
Vivian Miranda

C++で保証されている最速のサイン関数を次に示します。

double FastSin(double x)
{
    return 0;
}

ああ、あなたは| 1.0 |よりも良い精度を望んでいましたか?よく読んでください。

1970年代のエンジニアは、この分野でいくつかの素晴らしい発見をしましたが、新しいプログラマーは、これらの方法が標準のコンピューターサイエンスカリキュラムの一部として教えられていないため、これらの方法が存在することを単に知りません。

すべてのアプリケーションにこれらの関数の「完璧な」実装がないことを理解することから始める必要があります。したがって、「どれが最も速いか」などの質問に対する表面的な答えは間違いであることが保証されています。

この質問をするほとんどの人は、パフォーマンスと精度の間のトレードオフの重要性を理解していません。特に、他の操作を行う前に、計算の精度に関していくつかの選択を行う必要があります。結果にどの程度の誤差を許容できますか? 10 ^ -4? 10 ^ -16?

どの方法でもエラーを定量化できる場合を除いて、使用しないでください。ランダムな非コメント化されたソースを大量に投稿する以下のランダムな回答をすべて表示コード、使用されているアルゴリズムと入力範囲全体の最大誤差を明確に文書化せずに?それは厳密にブッシュリーグです。 サイン関数の正確な最大誤差を計算できない場合、サイン関数を書くことはできません。

ソフトウェアの超越を近似するためにテイラー級数を単独で使用する人はいません。特定の非常に特殊なケースを除いて、テイラー級数は一般に、一般的な入力範囲にわたってゆっくりとターゲットに近づきます

祖父母が超越を効率的に計算するために使用したアルゴリズムは、まとめて [〜#〜] cordic [〜#〜] と呼ばれ、ハードウェアに実装できるほど単純でした。 十分に文書化されたC のCORDIC実装です。通常、CORDIC実装には非常に小さなルックアップテーブルが必要ですが、ほとんどの実装ではハードウェア乗算器を使用する必要さえありません。ほとんどのCORDIC実装では、リンクしたものも含め、パフォーマンスと精度のトレードオフが可能です。

元のCORDICアルゴリズムには、長年にわたって多くの漸進的な改善がありました。たとえば、昨年、日本の一部の研究者は、回転角度が改善されたCORDICの article を公開し、必要な操作を減らしました。

ハードウェア乗算器を使用している場合(およびほぼ確実に実行できる場合)、またはCORDICが必要とするようなルックアップテーブルを購入できない場合は、常に Chebyshev多項式 を使用して同じこと。チェビシェフ多項式には乗算が必要ですが、これは現代のハードウェアではめったに問題になりません。 には、与えられた近似 に対して非常に予測可能な最大誤差があるため、チェビシェフ多項式が好きです。入力範囲にわたるチェビシェフ多項式の最後の項の最大値は、結果の誤差を制限します。また、このエラーは、用語の数が増えるにつれて小さくなります。 これは、正弦関数の自然な対称性を無視し、より多くの係数を投げることによって近似問題を解決するだけで、巨大な範囲にわたって正弦近似を与えるチェビシェフ多項式の一例 です。

また、近似の誤差が出力範囲全体に均等に分散されるため、チェビシェフ多項式も好きです。オーディオプラグインを作成している場合、またはデジタル信号処理を行っている場合、チェビシェフ多項式を使用すると、安価で予測可能なディザリング効果を「無料で」提供できます。

特定の範囲で独自のチェビシェフ多項式係数を見つけたい場合、多くの数学ライブラリは、それらの係数を見つけるプロセスを「 チェビシェフfit 」などと呼びます。

現在の平方根は、通常、 Newton-Raphsonアルゴリズム のバリアントを使用して計算され、通常は反復回数が固定されています。通常、誰かが平方根を行うために 「驚くべき新しい」 アルゴリズムを作成すると、それは単にニュートンラプソンに変装しています。

Newton-Raphson、CORDIC、およびChebyshevの多項式を使用すると、速度と精度のトレードオフが可能になるため、答えは必要なだけ不正確になる可能性があります。

最後に、すべての凝ったベンチマークとマイクロ最適化を終了したら、「高速」バージョンがライブラリバージョンよりも実際に高速であることを確認してください。 -pi/4からpi/4のドメインにバインドされたfsin() の典型的なライブラリ実装です。そして、それはそんなに遅くはありません。

これらの問題を効率的に解決することに自分の人生を捧げてきた人々がいて、彼らはいくつかの魅力的な結果を生み出しました。古い学校に参加する準備ができたら、 Numerical Recipes のコピーを取得します。

tl:dr; Googleの「正弦近似」または「余弦近似」または「平方根近似」または「 近似理論 。」

27
johnwbyrd

平方根には、ビットシフトと呼ばれるアプローチがあります。

IEEE-754で定義されている浮動小数点数は、複数のベース2の記述時間を表す特定のビットを使用しています。一部のビットは、ベース値を表すためのものです。

float squareRoot(float x)
{
  unsigned int i = *(unsigned int*) &x;

  // adjust bias
  i  += 127 << 23;
  // approximation of square root
  i >>= 1;

  return *(float*) &i;
}

それは平方根を計算する一定の時間です

19
BigTailWolf

http://forum.devmaster.net/t/fast-and-accurate-sine-cosine/9648 のアイデアと、マイクロベンチマークのパフォーマンスを改善するための手動の書き換えに基づいて次のコサイン実装は、多数のスペースで繰り返されるcos呼び出しによってボトルネックとなるHPC物理シミュレーションで使用されます。ルックアップテーブルよりも十分に正確で、はるかに高速です。特に、除算は不要です。

template<typename T>
inline T cos(T x) noexcept
{
    constexpr T tp = 1./(2.*M_PI);
    x *= tp;
    x -= T(.25) + std::floor(x + T(.25));
    x *= T(16.) * (std::abs(x) - T(.5));
    #if EXTRA_PRECISION
    x += T(.225) * x * (std::abs(x) - T(1.));
    #endif
    return x;
}

少なくともインテル®コンパイラーは、ループで使用された場合、この関数をベクトル化するのに十分スマートです。

EXTRA_PRECISIONが定義されている場合、Tdoubleであると仮定すると、ほとんどのC++実装で通常定義されるように、-πからπの範囲で最大誤差は約0.00109です。それ以外の場合、同じ範囲の最大誤差は約0.056です。

10
milianw

X86の場合、ハードウェアFP平方根命令は高速です(sqrtssはsqrtスカラー単精度)です。単精度は倍精度よりも高速です。そのため、コードではfloatの代わりにdoubleを使用してください。より少ない精度を使用する余裕があります。

32ビットコードの場合、通常、コンパイラオプションを使用して、FP math with SSE命令を使用します。たとえば、_-mfpmath=sse_)

Cのsqrt()またはsqrtf()関数をsqrtsdまたはsqrtssとしてインライン化するには、-fno-math-errno_でコンパイルする必要があります。 NaNでerrnoを設定する数学関数は、一般に設計ミスと見なされますが、標準ではそれが必要です。このオプションがない場合、gccはインライン化しますが、compare + branchを実行して結果がNaNであるかどうかを確認し、そうであれば、errnoを設定できるようにライブラリ関数を呼び出します。プログラムが数学関数の後にerrnoをチェックしない場合、_-fno-math-errno_を使用しても危険はありません。

sqrtやその他の関数をインライン化するために_-ffast-math_の「安全でない」部分は必要ありませんが、_-ffast-math_は大きな違いを生むことができます(例えば、コンパイラーが自動 FP mathは連想的ではない であるため、結果が変わる場合は-vectorize。

例えば gcc6.3コンパイルfloat foo(float a){ return sqrtf(a); }

_foo:    # with -O3 -fno-math-errno.
    sqrtss  xmm0, xmm0
    ret
_
_foo:   # with just -O3
    pxor    xmm2, xmm2   # clang just checks for NaN, instead of comparing against zero.
    sqrtss  xmm1, xmm0
    ucomiss xmm2, xmm0
    ja      .L8          # take the slow path if 0.0 > a
    movaps  xmm0, xmm1
    ret

.L8:                     # errno-setting path
    sub     rsp, 24
    movss   DWORD PTR [rsp+12], xmm1   # store the sqrtss result because the x86-64 SysV ABI has no call-preserved xmm regs.
    call    sqrtf                      # call sqrtf just to set errno
    movss   xmm1, DWORD PTR [rsp+12]
    add     rsp, 24
    movaps  xmm0, xmm1    # extra mov because gcc reloaded into the wrong register.
    ret
_

naNの場合のgccのコードは非常に複雑に思えます。 sqrtf戻り値さえ使用しません!とにかく、これはプログラムのsqrtf()呼び出しサイトごとに_-fno-math-errno_なしで実際に得られる混乱のようなものです。ほとんどは単なるコード膨張であり、0.0以上の数のsqrtを取得するときに_.L8_ブロックは実行されませんが、高速パスにはまだいくつかの余分な命令があります。


sqrtへの入力がゼロ以外であることがわかっている場合は、高速だが非常に近似した逆数sqrt命令rsqrtps(またはスカラーバージョンの場合はrsqrtss)を使用できます。 1つの Newton-Raphsonの繰り返し は、ハードウェアの単精度sqrt命令とほぼ同じ精度になりますが、完全ではありません。

sqrt(x) = x * 1/sqrt(x)、_x!=0_の場合、rsqrtと乗算でsqrtを計算できます。これらは両方ともP4でも高速です(2013年も引き続き関連していましたか?)。

P4では、rsqrt + Newton反復を使用して、単一のsqrtを置き換える価値があります(たとえそれで何も分割する必要がない場合でも)。

Newton Iterationでsqrt(x) としてx*rsqrt(x)を計算する際のゼロの処理について最近書いた回答も参照してください。 FP値を整数に変換する場合の丸め誤差に関する説明と、他の関連する質問へのリンクを含めました。


P4:

  • sqrtss:23cレイテンシー、パイプライン化されていません
  • sqrtsd:レイテンシー38c、パイプライン化されていません
  • fsqrt(x87):43cレイテンシー、パイプライン化されていません
  • rsqrtss/mulss:4c + 6cレイテンシ。おそらく同じ実行ユニット(mmx対fp)を必要としないため、おそらく3cスループットごとに1つです。

  • SIMDパックバージョンはやや遅い


スカイレイク:

  • sqrtss/sqrtps:12cレイテンシ、3cスループットごとに1
  • sqrtsd/sqrtpd:15-16cレイテンシ、4-6cスループットごとに1つ
  • fsqrt(x87):14-21ccのレイテンシ、4-7cのスループットごとに1つ
  • rsqrtss/mulss:4c + 4cレイテンシ。 1cスループットごとに1つ。
  • SIMD 128bベクターバージョンは同じ速度です。 256bベクターバージョンは、レイテンシが少し高く、スループットがほぼ半分です。 rsqrtssバージョンは、256bベクトルに対して完全なパフォーマンスを備えています。

Newton Iterationを使用すると、rsqrtバージョンはあまり高速ではありません。


Agner Fogの実験的テスト の数値。コードの高速化または低速化の原因を理解するには、彼のマイクロアーチガイドを参照してください。 x86 タグwikiのリンクも参照してください。

IDKは、sin/cosの最適な計算方法です。私は、ハードウェアfsin/fcos(および両方を同時に行うわずかに遅いfsincos)が最速の方法ではないことを読みましたが、IDKが何であるかを読みました。

5
Peter Cordes

QTには、補間付きルックアップテーブルを使用するサイン(qFastSin)およびコサイン(qFastCos)の高速実装があります。私は自分のコードでそれを使用していますが、std:sin/cosよりも高速で、必要なものに十分正確です(エラー〜= 0.01%と推測します):

https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qmath.h.html#_Z8qFastSind

#define QT_SINE_TABLE_SIZE 256


inline qreal qFastSin(qreal x)
{
   int si = int(x * (0.5 * QT_SINE_TABLE_SIZE / M_PI)); // Would be more accurate with qRound, but slower.
   qreal d = x - si * (2.0 * M_PI / QT_SINE_TABLE_SIZE);
   int ci = si + QT_SINE_TABLE_SIZE / 4;
   si &= QT_SINE_TABLE_SIZE - 1;
   ci &= QT_SINE_TABLE_SIZE - 1;
   return qt_sine_table[si] + (qt_sine_table[ci] - 0.5 * qt_sine_table[si] * d) * d;
}

inline qreal qFastCos(qreal x)
{
   int ci = int(x * (0.5 * QT_SINE_TABLE_SIZE / M_PI)); // Would be more accurate with qRound, but slower.
   qreal d = x - ci * (2.0 * M_PI / QT_SINE_TABLE_SIZE);
   int si = ci + QT_SINE_TABLE_SIZE / 4;
   si &= QT_SINE_TABLE_SIZE - 1;
   ci &= QT_SINE_TABLE_SIZE - 1;
   return qt_sine_table[si] - (qt_sine_table[ci] + 0.5 * qt_sine_table[si] * d) * d;
}

LUTとライセンスはこちらにあります: https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qmath.cpp.html#qt_sine_table

3
Adriel Jr

これは、以前に akelleheの答え で与えられたTaylor Seriesメソッドの実装です。

unsigned int Math::SIN_LOOP = 15;
unsigned int Math::COS_LOOP = 15;

// sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ...
template <class T>
T Math::sin(T x)
{
    T Sum       = 0;
    T Power     = x;
    T Sign      = 1;
    const T x2  = x * x;
    T Fact      = 1.0;
    for (unsigned int i=1; i<SIN_LOOP; i+=2)
    {
        Sum     += Sign * Power / Fact;
        Power   *= x2;
        Fact    *= (i + 1) * (i + 2);
        Sign    *= -1.0;
    }
    return Sum;
}

// cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ...
template <class T>
T Math::cos(T x)
{
    T Sum       = x;
    T Power     = x;
    T Sign      = 1.0;
    const T x2  = x * x;
    T Fact      = 1.0;
    for (unsigned int i=3; i<COS_LOOP; i+=2)
    {
        Power   *= x2;
        Fact    *= i * (i - 1);
        Sign    *= -1.0;
        Sum     += Sign * Power / Fact;
    }
    return Sum;
}
1
hkBattousai

私のコードを共有しているのは、6次多項式であり、powsを避けるために再配置された特別なものです。 Core i7では、[0..2 * PI]の範囲では少し高速ですが、これは標準実装の2.3倍遅くなります。古いプロセッサの場合、これは標準のsin/cosの代わりになる可能性があります。

/*
    On [-1000..+1000] range with 0.001 step average error is: +/- 0.000011, max error: +/- 0.000060
    On [-100..+100] range with 0.001 step average error is:   +/- 0.000009, max error: +/- 0.000034
    On [-10..+10] range with 0.001 step average error is:     +/- 0.000009, max error: +/- 0.000030
    Error distribution ensures there's no discontinuity.
*/

const double PI          = 3.141592653589793;
const double HALF_PI     = 1.570796326794897;
const double DOUBLE_PI   = 6.283185307179586;
const double SIN_CURVE_A = 0.0415896;
const double SIN_CURVE_B = 0.00129810625032;

double cos1(double x) {
    if (x < 0) {
        int q = -x / DOUBLE_PI;
        q += 1;
        double y = q * DOUBLE_PI;
        x = -(x - y);
    }
    if (x >= DOUBLE_PI) {
        int q = x / DOUBLE_PI;
        double y = q * DOUBLE_PI;
        x = x - y;
    }
    int s = 1;
    if (x >= PI) {
        s = -1;
        x -= PI;
    }
    if (x > HALF_PI) {
        x = PI - x;
        s = -s;
    }
    double z = x * x;
    double r = z * (z * (SIN_CURVE_A - SIN_CURVE_B * z) - 0.5) + 1.0;
    if (r > 1.0) r = r - 2.0;
    if (s > 0) return r;
    else return -r;
}

double sin1(double x) {
    return cos1(x - HALF_PI);
}
1
Josh

Wintelアプリ用にインラインx86でFPUを使用するだけです。直接CPUのsqrt関数は、他のアルゴリズムの速度を依然として上回っていると報告されています。私のカスタムx86 Mathライブラリコードは、標準のMSVC++ 2005以降のものです。私が説明したより高い精度が必要な場合は、個別のfloat/doubleバージョンが必要です。コンパイラの「__inline」戦略がうまくいかない場合があるので、安全のために削除できます。経験があれば、マクロに切り替えて、毎回関数呼び出しを完全に回避できます。

extern __inline float  __fastcall fs_sin(float x);
extern __inline double __fastcall fs_Sin(double x);
extern __inline float  __fastcall fs_cos(float x);
extern __inline double __fastcall fs_Cos(double x);
extern __inline float  __fastcall fs_atan(float x);
extern __inline double __fastcall fs_Atan(double x);
extern __inline float  __fastcall fs_sqrt(float x);
extern __inline double __fastcall fs_Sqrt(double x);
extern __inline float  __fastcall fs_log(float x);
extern __inline double __fastcall fs_Log(double x);

extern __inline float __fastcall fs_sqrt(float x) { __asm {
FLD x  ;// Load/Push input value
FSQRT
}}

extern __inline double __fastcall fs_Sqrt(double x) { __asm {
FLD x  ;// Load/Push input value
FSQRT
}}

extern __inline float __fastcall fs_sin(float x) { __asm {
FLD x  ;// Load/Push input value
FSIN
}}

extern __inline double __fastcall fs_Sin(double x) { __asm {
FLD x  ;// Load/Push input value
FSIN
}}    

extern __inline float __fastcall fs_cos(float x) { __asm {
FLD x  ;// Load/Push input value
FCOS
}}

extern __inline double __fastcall fs_Cos(double x) { __asm {
FLD x  ;// Load/Push input value
FCOS
}}

extern __inline float __fastcall fs_tan(float x) { __asm {
FLD x  ;// Load/Push input value
FPTAN
}}

extern __inline double __fastcall fs_Tan(double x) { __asm {
FLD x  ;// Load/Push input value
FPTAN
}}

extern __inline float __fastcall fs_log(float x) { __asm {
FLDLN2
FLD x
FYL2X
FSTP ST(1) ;// Pop1, Pop2 occurs on return
}}

extern __inline double __fastcall fs_Log(double x) { __asm {
FLDLN2
FLD x
FYL2X
FSTP ST(1) ;// Pop1, Pop2 occurs on return
}}
1
John Doe

次のコードを使用して、4倍精度で三角関数を計算します。定数Nは、必要な精度のビット数を決定します(たとえば、N = 26は単精度の精度を与えます)。必要な精度に応じて、事前計算されたストレージは小さく、キャッシュに収まります。加算と乗算の演算のみが必要で、ベクトル化も非常に簡単です。

このアルゴリズムは、0.5 ^ i、i = 1、...、Nのsin値とcos値を事前計算します。次に、これらの事前計算された値を組み合わせて、0.5 ^ Nの解像度までの任意の角度のsinとcosを計算します。

template <class QuadReal_t>
QuadReal_t sin(const QuadReal_t a){
  const int N=128;
  static std::vector<QuadReal_t> theta;
  static std::vector<QuadReal_t> sinval;
  static std::vector<QuadReal_t> cosval;
  if(theta.size()==0){
    #pragma omp critical (QUAD_SIN)
    if(theta.size()==0){
      theta.resize(N);
      sinval.resize(N);
      cosval.resize(N);

      QuadReal_t t=1.0;
      for(int i=0;i<N;i++){
        theta[i]=t;
        t=t*0.5;
      }

      sinval[N-1]=theta[N-1];
      cosval[N-1]=1.0-sinval[N-1]*sinval[N-1]/2;
      for(int i=N-2;i>=0;i--){
        sinval[i]=2.0*sinval[i+1]*cosval[i+1];
        cosval[i]=sqrt(1.0-sinval[i]*sinval[i]);
      }
    }
  }

  QuadReal_t t=(a<0.0?-a:a);
  QuadReal_t sval=0.0;
  QuadReal_t cval=1.0;
  for(int i=0;i<N;i++){
    while(theta[i]<=t){
      QuadReal_t sval_=sval*cosval[i]+cval*sinval[i];
      QuadReal_t cval_=cval*cosval[i]-sval*sinval[i];
      sval=sval_;
      cval=cval_;
      t=t-theta[i];
    }
  }
  return (a<0.0?-sval:sval);
}
1
Dhairya

100000000以上のテストでは、milianwの答えはstd :: cos実装よりも2倍遅いです。ただし、次の手順を実行することで、より高速に実行できます。

->フロートを使用

-> floorではなくstatic_castを使用します

-> absを使用せず、三項条件付き

->除算に#define定数を使用

->マクロを使用して関数呼び出しを回避する

// 1 / (2 * PI)
#define FPII 0.159154943091895
//PI / 2
#define PI2 1.570796326794896619

#define _cos(x)         x *= FPII;\
                        x -= .25f + static_cast<int>(x + .25f) - 1;\
                        x *= 16.f * ((x >= 0 ? x : -x) - .5f);
#define _sin(x)         x -= PI2; _cos(x);

Std :: cosおよび_cos(x)への100000000を超える呼び出し、std :: cosは_cos(x)の場合は〜14s対〜3sで実行されます(_sin(x)の場合はもう少し)

1
Hugo Zevetel

つまり、このアイデアは、Remezアルゴリズムを使用して、範囲[-pi/4、+ pi/4]のコサインとサイン関数を境界誤差で近似することに由来しています。次に、範囲縮小浮動小数点剰余と整数商の出力cos&sineのLUTを使用して、近似を任意のangular引数に移動できます。

それはまさにユニークなものであり、境界エラーに関してより効率的なアルゴリズムを作成するために拡張できると考えました。

void sincos_fast(float x, float *pS, float *pC){
     float cosOff4LUT[] = { 0x1.000000p+00,  0x1.6A09E6p-01,  0x0.000000p+00, -0x1.6A09E6p-01, -0x1.000000p+00, -0x1.6A09E6p-01,  0x0.000000p+00,  0x1.6A09E6p-01 };

    int     m, ms, mc;
    float   xI, xR, xR2;
    float   c, s, cy, sy;

    // Cody & Waite's range reduction Algorithm, [-pi/4, pi/4]
    xI  = floorf(x * 0x1.45F306p+00 + 0.5);
    xR  = (x - xI * 0x1.920000p-01) - xI*0x1.FB5444p-13;
    m   = (int) xI;
    xR2 = xR*xR;

    // Find cosine & sine index for angle offsets indices
    mc = (  m  ) & 0x7;     // two's complement permits upper modulus for negative numbers =P
    ms = (m + 6) & 0x7;     // two's complement permits upper modulus for negative numbers =P, note phase correction for sine.

    // Find cosine & sine
    cy = cosOff4LUT[mc];     // Load angle offset neighborhood cosine value 
    sy = cosOff4LUT[ms];     // Load angle offset neighborhood sine value 

    c = 0xf.ff79fp-4 + xR2 * (-0x7.e58e9p-4);               // TOL = 1.2786e-4
    // c = 0xf.ffffdp-4 + xR2 * (-0x7.ffebep-4 + xR2 * 0xa.956a9p-8);  // TOL = 1.7882e-7

     s = xR * (0xf.ffbf7p-4 + x2 * (-0x2.a41d0cp-4));   // TOL = 4.835251e-6
    // s = xR * (0xf.fffffp-4 + xR2 * (-0x2.aaa65cp-4 + xR2 * 0x2.1ea25p-8));  // TOL = 1.1841e-8

     *pC = c*cy - s*sy;     
    *pS = c*sy + s*cy;
}

float sqrt_fast(float x){
    union {float f; int i; } X, Y;
    float ScOff;
    uint8_t e;

    X.f = x;
    e = (X.i >> 23);           // f.SFPbits.e;

    if(x <= 0) return(0.0f);

    ScOff = ((e & 1) != 0) ? 1.0f : 0x1.6a09e6p0;  // NOTE: If exp=EVEN, b/c (exp-127) a (EVEN - ODD) := ODD; but a (ODD - ODD) := EVEN!!

    e = ((e + 127) >> 1);                            // NOTE: If exp=ODD,  b/c (exp-127) then flr((exp-127)/2)
    X.i = (X.i & ((1uL << 23) - 1)) | (0x7F << 23);  // Mask mantissa, force exponent to zero.
    Y.i = (((uint32_t) e) << 23);

    // Error grows with square root of the exponent. Unfortunately no work around like inverse square root... :(
    // Y.f *= ScOff * (0x9.5f61ap-4 + X.f*(0x6.a09e68p-4));        // Error = +-1.78e-2 * 2^(flr(log2(x)/2))
    // Y.f *= ScOff * (0x7.2181d8p-4 + X.f*(0xa.05406p-4 + X.f*(-0x1.23a14cp-4)));      // Error = +-7.64e-5 * 2^(flr(log2(x)/2))
    // Y.f *= ScOff * (0x5.f10e7p-4 + X.f*(0xc.8f2p-4 +X.f*(-0x2.e41a4cp-4 + X.f*(0x6.441e6p-8))));     // Error =  8.21e-5 * 2^(flr(log2(x)/2))
    // Y.f *= ScOff * (0x5.32eb88p-4 + X.f*(0xe.abbf5p-4 + X.f*(-0x5.18ee2p-4 + X.f*(0x1.655efp-4 + X.f*(-0x2.b11518p-8)))));   // Error = +-9.92e-6 * 2^(flr(log2(x)/2))
    // Y.f *= ScOff * (0x4.adde5p-4 + X.f*(0x1.08448cp0 + X.f*(-0x7.ae1248p-4 + X.f*(0x3.2cf7a8p-4 + X.f*(-0xc.5c1e2p-8 + X.f*(0x1.4b6dp-8))))));   // Error = +-1.38e-6 * 2^(flr(log2(x)/2))
    // Y.f *= ScOff * (0x4.4a17fp-4 + X.f*(0x1.22d44p0 + X.f*(-0xa.972e8p-4 + X.f*(0x5.dd53fp-4 + X.f*(-0x2.273c08p-4 + X.f*(0x7.466cb8p-8 + X.f*(-0xa.ac00ep-12)))))));    // Error = +-2.9e-7 * 2^(flr(log2(x)/2))
    Y.f *= ScOff * (0x3.fbb3e8p-4 + X.f*(0x1.3b2a3cp0 + X.f*(-0xd.cbb39p-4 + X.f*(0x9.9444ep-4 + X.f*(-0x4.b5ea38p-4 + X.f*(0x1.802f9ep-4 + X.f*(-0x4.6f0adp-8 + X.f*(0x5.c24a28p-12 ))))))));   // Error = +-2.7e-6 * 2^(flr(log2(x)/2))

    return(Y.f);
}
0
nimig18