How to wrap around a range

后端 未结 7 837
夕颜
夕颜 2020-12-05 04:39

Angles in my program are expressed in 0 to 2pi. I want a way to add two angles and have it wrap around the 2pi to 0 if the result is higher than 2pi. Or if I subtracted an a

7条回答
  •  再見小時候
    2020-12-05 05:30

    angle = fmod(angle, 2.0 * pi);
    if (angle < 0.0)
       angle += 2.0 * pi;
    

    Edit: After re-reading this (and looking at Jonathan Leffler's answer) I was a bit surprised by his conclusion, so I rewrote the code to what I considered a somewhat more suitable form (e.g., printing out a result from the computation to ensure the compiler couldn't just discard the computation completely because it was never used). I also changed it to use the Windows performance counter (since he didn't include his timer class, and the std::chrono::high_resolution_timer is completely broken in both the compilers I have handy right now).

    I also did a bit of general code cleanup (this is tagged C++, not C), to get this:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    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 
    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 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";
    
    }
    

    The results I got were as follows:

    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
    

    At least to me, the results seem to support a rather different conclusion than Jonathon reached. Looking at the version that does subtraction in a loop, we see two points: for the large angles test it produces a sum that's different from the other two (i.e., it's inaccurate) and second, it's horribly slow. Unless you know for certain that your inputs always start out nearly normalized, this is basically just unusable.

    Between the fmod version and the floor version there seems to be no room for argument--they both produce accurate results, but the fmod version is faster in both the small angle and large angle tests.

    I did a bit more testing, experimenting with increasing the number of repetitions and decreasing the step sizes in the large angles test. Although I suppose it's possible it's simply due to a difference in platform or compiler, I was unable to find any circumstance or situation that even came close to upholding Jonathan's results or conclusion.

    Bottom line: if you have a lot of prior knowledge about your input, and know it'll always be nearly normalized before you normalize it, then you might be able to get away with doing subtraction in a loop. Under any other circumstance, fmod is the clear choice. There seems to be no circumstance in which the floor version makes any sense at all.

    Oh, for what it's worth:
    OS: Windows 7 ultimate
    Compiler: g++ 4.9.1
    Hardware: AMD A6-6400K
    

提交回复
热议问题