29

私のプログラムでは、角度は 0 から 2pi で表されます。2 つの角度を追加し、結果が 2pi より大きい場合は 2pi を 0 にラップする方法が必要です。または、角度から角度を差し引いた値が 0 未満の場合、2pi にラップします。

これを行う方法はありますか?

ありがとう。

4

8 に答える 8

55

あなたが探しているのはモジュラスです。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 は入力以下の最大の整数です。

于 2012-08-16T03:33:09.280 に答える
20
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 の結果や結論を支持するに近い状況や状況を見つけることさえできませんでした。

結論: 入力について多くの予備知識があり、正規化する前に常にほぼ正規化されることがわかっている場合は、ループ内で減算を実行することで回避できる可能性があります。他の状況下では、明確な選択です。バージョンがまったく意味をなさない状況はないようです。fmodfloor

Oh, for what it's worth:
OS: Windows 7 ultimate
Compiler: g++ 4.9.1
Hardware: AMD A6-6400K
于 2012-08-16T03:26:53.413 に答える
8

好奇心から、他の回答で 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
于 2012-08-16T04:30:00.893 に答える
4

次のようなものを使用できます。

while (angle > 2pi)
    angle -= 2pi;

while (angle < 0)
    angle += 2pi;

基本的に、角度が 2pi を超えたり 0 を下回ったりしないことが保証されるまで、角度を 2pi ずつ変更する必要があります。

于 2012-08-16T03:34:43.483 に答える
0

同様に、-2pi から 2pi の範囲になりたい場合は、fmod がうまく機能します

于 2017-09-19T01:12:17.213 に答える