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
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