20

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

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

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

4

6 に答える 6

35

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

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

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

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

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

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. ゼロを中心とするテイラー級数展開は次のとおりです。ゼロに近いほど正確

したがって、切り捨てられたテイラー級数を使用することにした場合は、次のような恒等式を使用して、サインまたはコサインのいずれかにマッピングして [0,pi/4] の範囲の角度を取得することにより、精度を向上させることができます (または同様の精度のために使用する項を減らします)。上記のものに加えて、sin(x) = cos(pi/2-x) および cos(x) = sin(pi/2-x) (たとえば、一度 x > pi/4 にマッピングした場合)最初の象限。)

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

于 2010-09-10T23:55:01.067 に答える
5

もう 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)

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

于 2010-09-19T17:37:43.377 に答える
5
long double sine_table[2001];
for (int index = 0; index < 2001; index++)
{
    sine_table[index] = std::sin(PI * (index - 1000) / 1000.0);
}
于 2010-09-10T22:03:52.287 に答える
3


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 まで格納します。頭の中で覚えていませんが、プログラムの速度が向上しました。

于 2010-09-10T23:29:04.593 に答える
2

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

于 2010-09-10T22:00:57.957 に答える
2

本か何かからの別の概算

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);
于 2013-03-29T22:14:09.333 に答える