59

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

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

では、質問: C++ で float のサイン、コサイン、平方根を計算する最速の方法は何ですか?

編集 ルックアップ テーブルは、最新の CPU ではテイラー シリーズよりもキャッシュ ミスのコストが高くなるため、より苦痛です。最近の CPU は非常に高速ですが、キャッシュはそうではありません。

私は間違いを犯しました。テイラー級数のいくつかの階乗を計算する必要があると思いましたが、定数として実装できることがわかりました。

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

EDIT2

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

EDIT3

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

4

18 に答える 18

98

C++ で保証されている最速の正弦関数は次のとおりです。

double FastSin(double x)
{
    return 0;
}

|1.0| よりも高い精度が必要でしたか? さて、これは同様に高速な正弦関数です。

double FastSin(double x)
{
    return x;
}

xがゼロに近い場合、 この答えは実際には吸わない。x が小さい場合、sin(x) は x にほぼ等しくなります。これは、x が sin(x) のテイラー展開の最初の項であるためです。

まだ十分に正確ではありませんか?よく読んでください。

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

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

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

いずれかの方法でエラーを定量化できない限り、使用しないでください。使用されたアルゴリズムと入力範囲全体での正確な最大誤差を 明確に文書化せずに、コメントなしのランダムなソースコードの束を投稿する、私の以下のランダムな回答をすべて参照してください。「エラーは、私が推測する、おおよそのつぶやきのようなものです。」それは厳密にはブッシュリーグです。入力の全範囲にわたって、近似関数でPRECISE最大誤差を完全な精度で計算する方法がわからない場合は、近似関数の書き方がわかりません。

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

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

元の CORDIC アルゴリズムには、何年にもわたって多くの改良が加えられてきました。たとえば、昨年、日本の一部の研究者は、必要な操作を減らす、回転角度が改善された改良型 CORDICに関する記事を発表しました。

ハードウェア乗算器が手元にある場合 (ほぼ確実にそうです)、または CORDIC が必要とするようなルックアップ テーブルを用意できない場合は、いつでもチェビシェフ多項式を使用して同じことを行うことができます。チェビシェフ多項式には乗算が必要ですが、最新のハードウェアではほとんど問題になりません。チェビシェフ多項式は、与えられた近似に対して高度に予測可能な最大誤差を持つため、私たちはチェビシェフ多項式を好みます。入力範囲にわたるチェビシェフ多項式の最後の項の最大値は、結果の誤差を制限します。そして、この誤差は項数が増えるほど小さくなります。 ここに一例があります巨大な範囲にわたって正弦近似を与えるチェビシェフ多項式の、正弦関数の自然な対称性を無視し、より多くの係数を投げることによって近似問題を解くだけです。 そして、正弦関数を 5 ULP 以内に推定する例を次に示します。ULP が何かわかりませんか? あなたがすべき。

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

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

平方根は、現在と同様に、通常、ニュートン ラフソン アルゴリズムのいくつかの変形を使用して計算され、通常は反復回数が固定されています。通常、誰かが平方根を計算するための「驚くべき新しい」アルゴリズムを開発するとき、それは変装したニュートン ラフソンにすぎません。

Newton-Raphson、CORDIC、および Chebyshev 多項式を使用すると、速度と精度をトレードオフできるため、必要なだけ不正確な解を求めることができます。

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

最後に 1 つ注意してください。推定を実行するために IEEE-754 数学を使用していることはほぼ間違いありません。IEEE-754 数学を多数の乗算で実行している場合はいつでも、何十年も前に行われたあいまいなエンジニアリング上の決定が戻ってきます。あなた、丸め誤差の形で。そして、これらのエラーは最初は小さくなりますが、大きくなり、大きくなり、さらに大きくなります! 人生のある時点で、「すべてのコンピューター科学者が浮動小数点数について知っておくべきこと」を読んでください。適切な量​​の恐怖を持っています。独自の超越関数を書き始める場合は、理論上の最大誤差だけでなく、浮動小数点の丸めによる実際の誤差をベンチマークして測定する必要があることに注意してください。これは理論上の問題ではありません。「高速計算」コンパイル設定は、複数のプロジェクトで私を苦しめました。

tl:dr; Google で「正弦近似」または「余弦近似」または「平方根近似」または「近似理論」にアクセスしてください。

于 2016-06-21T14:43:44.807 に答える
30

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 が定義されている場合、ほとんどの C++ 実装で通常定義されているとT仮定すると、最大誤差は -π から π の範囲で約 0.00109 です。doubleそれ以外の場合、同じ範囲の最大誤差は約 0.056 です。

于 2015-01-20T16:24:40.007 に答える
24

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

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;
}

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

于 2013-09-06T16:46:13.590 に答える
8

x86 の場合、ハードウェア FP 平方根命令は高速です ( sqrtssis sqrt Scalar Single-precision)。単精度は倍精度よりも高速であるため、より低い精度を使用できるコードでは、float代わりに必ず使用してください。double

32 ビット コードの場合、通常、x87 ではなく SSE 命令を使用して FP 演算を行うには、コンパイラ オプションが必要です。(例-mfpmath=sse)

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

-ffast-mathgetの「安全でない」部分sqrtや他の関数をインライン化する必要はありませんが-ffast-math、大きな違いを生む可能性があります (たとえば、結果が変わる場合にコンパイラが自動ベクトル化できるようにするなど)。FP 演算は連想的ではありません

たとえば、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戻り値さえ使用しません! -fno-math-errnoとにかく、これは、プログラム内のすべてのsqrtf()呼び出しサイトで、がなくても実際に発生する種類の混乱です。ほとんどの場合、コードの肥大化であり、.L80.0 以上の数値の sqrt を取得するときにブロックは実行されませんが、高速パスにはまだいくつかの余分な命令があります。


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

sqrt(x) = x * 1/sqrt(x)、 forx!=0であるため、rsqrt と乗算を使用して sqrt を計算できます。これらは両方とも、P4 でも高速です (2013 年にはまだ関係がありましたか)?

P4 では、それで何かを割る必要がなくても、rsqrt+ ニュートン反復を使用して単一の を置き換える価値があるかもしれません。sqrt

ニュートン反復でとして計算するときにゼロを処理することについて最近書いた回答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 の実験的テストからの数値。彼のマイクロアーチ ガイドを参照して、コードの実行速度が速くなったり遅くなったりする原因を理解してください。タグ wikiのリンクも参照してください。

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

于 2016-04-16T05:39:51.097 に答える
6

QT には、補間付きのルックアップ テーブルを使用し、任意の入力値 (0 ~ 2PI の範囲外であっても) をカバーする正弦 (qFastSin) および余弦 (qFastCos) の高速実装があります。私は自分のコードでそれを使用していますが、std:sin/cos よりも高速で (~5 倍高速)、必要なものに対して十分に正確です (std::sin/cos との最大の差は ~0.00000246408 です):

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

これらの関数のペアは、ラジアン入力を受け取ります。LUT は 2π 入力範囲全体をカバーします。& 演算子は周期性を修正し、si (sin インデックス) と ci (cos インデックス) の値を LUT 範囲内に戻します。dこの関数は、導関数として余弦 (正弦を使用した同様の補間) を使用して、差 を使用して値の間を補間します。

于 2018-10-16T17:37:39.787 に答える
4

次のCORDICコードを使用して、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);
}
于 2015-01-15T15:16:00.840 に答える
2

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

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

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);              // This is 4/pi.
    xR  = (x - xI * 0x1.920000p-01) - xI*0x1.FB5444p-13; // This is pi/4 in two parts per C&W.
    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;     // 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 + xR2 * (-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);
}

長い式は長く、遅くなりますが、より正確になります。多項式は、ホーナーの規則に従って書かれています。

于 2017-04-03T04:29:35.333 に答える
2

導関数を 90 度の倍数で保持する正弦関数の近似は、次ので与えられます。導関数はBhaskara I の正弦近似式に似ていますが、値と導関数を正弦関数の値に対して 0、90、180 度に設定するという制約があります。関数をどこでもスムーズにする必要がある場合は、これを使用できます。

#define PI 3.141592653589793

double fast_sin(double x) {
    x /= 2 * PI;
    x -= (int) x;

    if (x <= 0.5) {
        double t = 2 * x * (2 * x - 1);
        return (PI * t) / ((PI - 4) * t - 1);
    }
    else {
        double t = 2 * (1 - x) * (1 - 2 * x);
        return -(PI * t) / ((PI - 4) * t - 1);
    }
}

double fast_cos(double x) {
    return fast_sin(x + 0.5 * PI);
}

速度に関しては、少なくともstd::sin()呼び出しあたり平均 0.3 マイクロ秒だけ関数を上回っています。また、最大絶対誤差は 0.0051 です。

于 2020-06-17T09:04:03.820 に答える
0

アプリケーションに大きく依存する高速化の可能性を次に示します。(あなたのプログラムはこれをまったく使用できないかもしれませんが、可能性があるのでここに投稿します。) また、数学をここに投稿しているだけです。コードはあなた次第です。

私のアプリケーションでは、完全な円の周りの角度ステップ (dA) ごとにサインとコサインを計算する必要がありました。

これにより、いくつかのトリガー ID を利用できるようになりました。

cos(-A) = cos(A)

sin(-A) = -sin(A)

これにより、円の半分の sin と cos を計算するだけで済みました。

また、出力配列へのポインターも設定しました。これにより、計算も高速化されました。これについてはよくわかりませんが、コンパイラが計算をベクトル化したと思います。

3 番目は、次を使用することでした。

sin(A+dA) = sin(a)*cos(dA) + cos(a)*sin(dA)

cos(a+dA) = cos(a)*cos(dA) - sin(a)*sin(dA)

これにより、1 つの角度の sin と cos を実際に計算するだけで済み、残りはそれぞれ 2 つの乗算と加算で計算されました。(これは、sin(dA) と cos(dA) の計算における丸め誤差が、円の半分に到達するまでに累積する可能性があることに注意してください。繰り返しますが、これを使用する場合、アプリケーションはすべてです。)

于 2020-12-08T17:42:12.443 に答える
-1

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 は ~14 秒で実行されるのに対し、_cos(x) では ~3 秒 (_sin(x) ではもう少し)

于 2016-05-11T07:26:11.927 に答える