私のプログラムでは、角度は 0 から 2pi で表されます。2 つの角度を追加し、結果が 2pi より大きい場合は 2pi を 0 にラップする方法が必要です。または、角度から角度を差し引いた値が 0 未満の場合、2pi にラップします。
これを行う方法はありますか?
ありがとう。
あなたが探しているのはモジュラスです。fmod 関数は、算術係数ではなく剰余を計算するため、機能しません。このようなものが動作するはずです:
inline double wrapAngle( double angle )
{
double twoPi = 2.0 * 3.141592865358979;
return angle - twoPi * floor( angle / twoPi );
}
編集:
剰余は、通常、長い除算の後に残ったものとして定義されます (たとえば、 18 = 4 * 4 + 2であるため、18/4 の剰余は2です)。負の数があると、これは毛むくじゃらになります。符号付き除算の剰余を見つける一般的な方法は、剰余が結果と同じ符号を持つようにすることです (たとえば、-18 = -4 * 4 + -2であるため、-18/4 の剰余は -2です)。
x モジュラス y の定義は、c が整数の場合、式 x=y*c+m における m の最小の正の値です。したがって、18 mod 4は 2 (c=4) になりますが、-18 mod 4も 2 (c=-5) になります。
x mod yの最も単純な計算はxy*floor(x/y)です。ここで、floor は入力以下の最大の整数です。
angle = fmod(angle, 2.0 * pi);
if (angle < 0.0)
angle += 2.0 * pi;
編集:これを読み直した後(そしてJonathan Lefflerの答えを見て)、彼の結論に少し驚いたので、コードを書き直して、より適切な形式と考えられるようにしました(たとえば、計算の結果を出力して確実にコンパイラは、計算がまったく使用されていないため、計算を完全に破棄することはできませんでした)。また、Windows パフォーマンス カウンターを使用するように変更しました (彼は彼のタイマー クラスを含めておらず、std::chrono::high_resolution_timer
現在手元にある両方のコンパイラーで完全に壊れているため)。
これを取得するために、一般的なコードのクリーンアップも少し行いました (これは C ではなく C++ とタグ付けされています)。
#include <math.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <windows.h>
static const double PI = 3.14159265358979323844;
static double r1(double angle)
{
while (angle > 2.0 * PI)
angle -= 2.0 * PI;
while (angle < 0)
angle += 2.0 * PI;
return angle;
}
static double r2(double angle)
{
angle = fmod(angle, 2.0 * PI);
if (angle < 0.0)
angle += 2.0 * PI;
return angle;
}
static double r3(double angle)
{
double twoPi = 2.0 * PI;
return angle - twoPi * floor(angle / twoPi);
}
struct result {
double sum;
long long clocks;
result(double d, long long c) : sum(d), clocks(c) {}
friend std::ostream &operator<<(std::ostream &os, result const &r) {
return os << "sum: " << r.sum << "\tticks: " << r.clocks;
}
};
result operator+(result const &a, result const &b) {
return result(a.sum + b.sum, a.clocks + b.clocks);
}
struct TestSet { double start, end, increment; };
template <class F>
result tester(F f, TestSet const &test, int count = 5)
{
LARGE_INTEGER start, stop;
double sum = 0.0;
QueryPerformanceCounter(&start);
for (int i = 0; i < count; i++) {
for (double angle = test.start; angle < test.end; angle += test.increment)
sum += f(angle);
}
QueryPerformanceCounter(&stop);
return result(sum, stop.QuadPart - start.QuadPart);
}
int main() {
std::vector<TestSet> tests {
{ -6.0 * PI, +6.0 * PI, 0.01 },
{ -600.0 * PI, +600.0 * PI, 3.00 }
};
std::cout << "Small angles:\n";
std::cout << "loop subtraction: " << tester(r1, tests[0]) << "\n";
std::cout << " fmod: " << tester(r2, tests[0]) << "\n";
std::cout << " floor: " << tester(r3, tests[0]) << "\n";
std::cout << "\nLarge angles:\n";
std::cout << "loop subtraction: " << tester(r1, tests[1]) << "\n";
std::cout << " fmod: " << tester(r2, tests[1]) << "\n";
std::cout << " floor: " << tester(r3, tests[1]) << "\n";
}
私が得た結果は次のとおりです。
Small angles:
loop subtraction: sum: 59196 ticks: 684
fmod: sum: 59196 ticks: 1409
floor: sum: 59196 ticks: 1885
Large angles:
loop subtraction: sum: 19786.6 ticks: 12516
fmod: sum: 19755.2 ticks: 464
floor: sum: 19755.2 ticks: 649
少なくとも私には、結果はジョナソンが到達した結論とはかなり異なる結論を支持しているように見えます. ループで減算を行うバージョンを見ると、2 つの点が分かります。大きな角度のテストでは、他の 2 つとは異なる合計が生成されます (つまり、不正確です)。2 つ目は、非常に遅いことです。入力が常にほぼ正規化されていることが確実にわかっていない限り、これは基本的に使用できません。
fmod
バージョンとバージョンの間floor
に議論の余地はないようです。どちらも正確な結果を生成しますがfmod
、小角度テストと大角度テストの両方でバージョンの方が高速です。
私はもう少しテストを行い、大きな角度のテストで繰り返し回数を増やし、ステップ サイズを減らして実験しました。単純にプラットフォームやコンパイラの違いが原因である可能性はあると思いますが、 Jonathan の結果や結論を支持するに近い状況や状況を見つけることさえできませんでした。
結論: 入力について多くの予備知識があり、正規化する前に常にほぼ正規化されることがわかっている場合は、ループ内で減算を実行することで回避できる可能性があります。他の状況下では、明確な選択です。バージョンがまったく意味をなさない状況はないようです。fmod
floor
Oh, for what it's worth:
OS: Windows 7 ultimate
Compiler: g++ 4.9.1
Hardware: AMD A6-6400K
好奇心から、他の回答で 3 つのアルゴリズムを試し、タイミングを計りました。
正規化する値が 0..2π の範囲に近い場合、while
アルゴリズムは最も高速です。使用するアルゴリズムfmod()
が最も遅く、使用するアルゴリズムfloor()
はその中間です。
正規化される値が範囲 0..2π に近くない場合、while
アルゴリズムは最も遅くなり、使用するアルゴリズムfloor()
は最も速くなり、使用するアルゴリズムfmod()
はその中間になります。
したがって、私は次のように結論付けています。
while
アルゴリズムが使用されます。floor()
アルゴリズムが使用されます。r1 = while
、r2 = fmod()
、r3 =floor()
Near Normal Far From Normal
r1 0.000020 r1 0.000456
r2 0.000078 r2 0.000085
r3 0.000058 r3 0.000065
r1 0.000032 r1 0.000406
r2 0.000085 r2 0.000083
r3 0.000057 r3 0.000063
r1 0.000033 r1 0.000406
r2 0.000085 r2 0.000085
r3 0.000058 r3 0.000065
r1 0.000033 r1 0.000407
r2 0.000086 r2 0.000083
r3 0.000058 r3 0.000063
テスト コードは、 に示されている値を使用しましたPI
。C 標準では π の値は定義されていませんが、POSIXM_PI
ではおよび関連する多くの定数が定義されているため、代わりに を使用してコードを記述できたはずM_PI
ですPI
。
#include <math.h>
#include <stdio.h>
#include "timer.h"
static const double PI = 3.14159265358979323844;
static double r1(double angle)
{
while (angle > 2.0 * PI)
angle -= 2.0 * PI;
while (angle < 0)
angle += 2.0 * PI;
return angle;
}
static double r2(double angle)
{
angle = fmod(angle, 2.0 * PI);
if (angle < 0.0)
angle += 2.0 * PI;
return angle;
}
static double r3(double angle)
{
double twoPi = 2.0 * PI;
return angle - twoPi * floor( angle / twoPi );
}
static void tester(const char * tag, double (*test)(double), int noisy)
{
typedef struct TestSet { double start, end, increment; } TestSet;
static const TestSet tests[] =
{
{ -6.0 * PI, +6.0 * PI, 0.01 },
// { -600.0 * PI, +600.0 * PI, 3.00 },
};
enum { NUM_TESTS = sizeof(tests) / sizeof(tests[0]) };
Clock clk;
clk_init(&clk);
clk_start(&clk);
for (int i = 0; i < NUM_TESTS; i++)
{
for (double angle = tests[i].start; angle < tests[i].end; angle += tests[i].increment)
{
double result = (*test)(angle);
if (noisy)
printf("%12.8f : %12.8f\n", angle, result);
}
}
clk_stop(&clk);
char buffer[32];
printf("%s %s\n", tag, clk_elapsed_us(&clk, buffer, sizeof(buffer)));
}
int main(void)
{
tester("r1", r1, 0);
tester("r2", r2, 0);
tester("r3", r3, 0);
tester("r1", r1, 0);
tester("r2", r2, 0);
tester("r3", r3, 0);
tester("r1", r1, 0);
tester("r2", r2, 0);
tester("r3", r3, 0);
tester("r1", r1, 0);
tester("r2", r2, 0);
tester("r3", r3, 0);
return(0);
}
/usr/bin/gcc
標準( )を使用した Mac OS X 10.7.4 でのテストi686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.9.00)
。「正規化に近い」テスト コードが表示されます。「正規化されていない」テスト データは、テスト データ内のコメントのコメントを解除することによって作成されました//
。
自作の GCC 4.7.1 のタイミングも同様です (同じ結論が導き出されます)。
Near Normal Far From Normal
r1 0.000029 r1 0.000321
r2 0.000075 r2 0.000094
r3 0.000054 r3 0.000065
r1 0.000028 r1 0.000327
r2 0.000075 r2 0.000096
r3 0.000053 r3 0.000068
r1 0.000025 r1 0.000327
r2 0.000075 r2 0.000101
r3 0.000053 r3 0.000070
r1 0.000028 r1 0.000332
r2 0.000076 r2 0.000099
r3 0.000050 r3 0.000065
次のようなものを使用できます。
while (angle > 2pi)
angle -= 2pi;
while (angle < 0)
angle += 2pi;
基本的に、角度が 2pi を超えたり 0 を下回ったりしないことが保証されるまで、角度を 2pi ずつ変更する必要があります。
同様に、-2pi から 2pi の範囲になりたい場合は、fmod がうまく機能します