web-dev-qa-db-ja.com

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

次の擬似コードをC++で書き直すにはどうすればよいですか?

real array sine_table[-1000..1000]
    for x from -1000 to 1000
        sine_table[x] := sine(pi * x / 1000)

Sine_tableルックアップテーブルを作成する必要があります。

15
user466444

最初の象限の値、つまりxの値を[0、pi/2]に格納するだけで、テーブルのサイズを元のサイズの25%に減らすことができます。

これを行うには、ルックアップルーチンは、単純なトリガーIDを使用してxのすべての値を第1象限にマップする必要があります。

  • sin(x)= --sin(-x)、象限IVからIにマッピング
  • sin(x)= sin(pi-x)、象限IIからIにマッピング

象限IIIからIにマッピングするには、両方のIDを適用します。つまり、sin(x)= --sin(pi + x)

この戦略が役立つかどうかは、ケースで重要なメモリ使用量によって異なります。ただし、ルックアップ中に比較と減算を回避するために、必要な4倍の値を格納するのは無駄に思えます。

2番目に、テーブルの作成がstd :: sin()を使用するよりも優れているかどうかを測定するというJeremyの推奨事項です。元の大きなテーブルを使用する場合でも、引数をpi/1000の最も近い増分に変換するために、各テーブルルックアップ中にサイクルを費やす必要があり、プロセスの精度がいくらか失われます。

精度と速度を実際に交換しようとしている場合は、テイラー級数展開の最初のいくつかの項だけを使用して、sin()関数を近似してみてください。

  • sin(x)= x-x ^ 3/3! + x ^ 5/5! ...、ここで^は累乗を表し、!階乗を表します。

もちろん、効率を上げるために、階乗を事前に計算し、xの低い累乗を利用して高い階乗を計算する必要があります。 x ^ 5を計算するときは、x ^ 3を使用します。

最後に、上記の切り捨てられたテイラー級数は、ゼロに近い値の方が正確であるため、近似正弦を計算する前に、第1象限または第4象限にマップする価値があります。

補遺:2つの観察に基づくさらにもう1つの潜在的な改善:
1。最初のオクタント[0、pi/4]で正弦と余弦の両方を計算できる場合は、任意の三角関数を計算できます。
2。ゼロを中心とするテイラー級数展開は、ゼロ付近でより正確です

したがって、切り捨てられたテイラー系列を使用する場合は、正弦または余弦のいずれかにマッピングして、次のようなIDを使用して[0、pi/4]の範囲の角度を取得することにより、精度を向上させる(または同様の精度のために使用する項を少なくする)ことができます。上記のものに加えて、sin(x)= cos(pi/2-x)およびcos(x)= sin(pi/2-x)(たとえば、にマップした後、x> pi/4の場合)第1象限。)

または、サインとコサインの両方にテーブルルックアップを使用することにした場合は、[0、pi/4]の範囲のみをカバーする2つの小さなテーブルを使用して、ルックアップでの別の可能な比較と減算を犠牲にして、マップすることができます。小さい範囲。次に、テーブルに使用するメモリを少なくするか、同じメモリを使用して、より細かい粒度と精度を提供することができます。

30
Alex Blakemore

_<cmath>_のstd::sin()関数が必要になります。

5
Mike DeSimone

もう1つのポイント:三角関数の呼び出しは高価です。一定のステップで正弦波のルックアップテーブルを準備する場合は、精度が低下する可能性がありますが、計算時間を節約できます。

最小ステップが「a」であると考えてください。つまり、sin(a)、sin(2a)、sin(3a)、..が必要です。

次に、次のトリックを実行できます。最初にsin(a)とcos(a)を計算します。次に、連続するすべてのステップに対して、次の三角関数の等式を使用します。

  • sin([n + 1] * a)= sin(n * a)* cos(a)+ cos(n * a)* sin(a)
  • cos([n + 1] * a)= cos(n * a)* cos(a)-sin(n * a)* sin(a)

この方法の欠点は、この手順中に丸め誤差が累積されることです。

4
valdo
long double sine_table[2001];
for (int index = 0; index < 2001; index++)
{
    sine_table[index] = std::sin(PI * (index - 1000) / 1000.0);
}
4
Svisstack

double table[1000] = {0};
for (int i = 1; i <= 1000; i++)
{
    sine_table[i-1] = std::sin(PI * i/ 1000.0);
}


double getSineValue(int multipleOfPi){ if(multipleOfPi == 0) return 0.0; int sign = 1; if(multipleOfPi < 0){ sign = -1;
} return signsine_table[signmultipleOfPi - 1]; }

トリックsin(pi/2 +/- angle)= +/- cos(angle)により、配列の長さを500に減らすことができます。したがって、sinとcosを0からpi/4まで保存します。頭のてっぺんからは覚えていませんが、プログラムのスピードが上がりました。

3
Master Yoda

本か何かからの別の近似

streamin ramp;
streamout sine;

float x,rect,k,i,j;

x = ramp -0.5;

rect = x * (1 - x < 0 & 2);
k = (rect + 0.42493299) *(rect -0.5) * (rect - 0.92493302) ;
i = 0.436501 + (rect * (rect + 1.05802));
j = 1.21551 + (rect * (rect - 2.0580201));
sine = i*j*k*60.252201*x;

ここでの完全な議論: http://synthmaker.co.uk/forum/viewtopic.php?f=4&t=6457&st=0&sk=t&sd=a

除算を使用すると、10進数を乗算するよりもはるかに遅く、/ 5は常に* 0.2よりも遅くなることをご存知だと思います。

これは単なる概算です。

また:

streamin ramp;
streamin x;  // 1.5 = Saw   3.142 = Sin   4.5 = SawSin
streamout sine;
float saw,saw2;
saw = (ramp * 2 - 1) * x;
saw2 = saw * saw;

sine = -0.166667 + saw2 * (0.00833333 + saw2 * (-0.000198409 + saw2 * (2.7526e-006+saw2 * -2.39e-008)));
sine = saw * (1+ saw2 * sine);
3
com.prehensible