Continuing from my earlier post, here’s another look at how we can abuse the compiler and C++11’s compile-time abilities. This one might even be useful!
The Problem
Trigonometry functions are common in a lot of programming. On modern hardware, they’re usually highly optimized, too. But C++ developers are taught to use the compiler as much as possible. That’s why we have templates and constexpr
, after all. So, can we do trig calculations this way?
The answer, of course, is “yes”. (If it wasn’t, then this post wouldn’t exist.) We’ll start with the cosine function, since it’s probably the easiest to implement. One way to define the cosine function is as an infinite series that I don’t really want to try to recreate in math notation here. As it turns out, that definition is fairly easily convertible to a recursive function that can be evaluated at compile-time.
The Code
template <int Iterations = 10>
constexpr double cosine (double theta)
{
return (power(-1, Iterations) * power(theta, 2 * Iterations)) /
static_cast<double>(factorial(2ull * Iterations))
+ cosine<Iterations-1>(theta);
}
template <>
constexpr double cosine<0> (double theta)
{
return 1.0;
}
Since the Taylor series is an infinite one, we obviously can’t run the whole thing. So we approximate. I’ve decided on a default of 10 iterations, but the function, you should note, actually calculates the series “in reverse”. The base (i.e., last) case, when Iterations == 0
, is the first term in the series expansion. It’s okay, though. The algorithm still works this way, although it might look a little weird.
Also, note that this cosine function uses two other constexpr
functions. The first, power()
, is our creation from last time, slightly renamed. The second is a typical factorial function. (It’s a good thing every single constexpr
tutorial uses this one, otherwise I’d have to write it myself!) factorial
takes an integer and returns another one, so I used an explicit cast to double
. You could just change the definition of factorial
, though, if you don’t mind the possible loss of precision. You’d probably want to template the whole thing on the type, too, instead of only using double
s.
The other trig functions can, for the most part, work off this base. The sine series raises the angle to the power of 2n+1
instead of 2n
(and takes the factorial of 2n+1
, as well), but it’s otherwise the same. Tangent, as you know, is sine divided by cosine. The reciprocals (secant, cosecant, cotangent) are trivial. You’d also need conversions between degrees and radians (this function uses radians), but that’s so easy you can do it in your sleep.
In a real application, you might want to combine this with lookup tables or something, especially because of rounding errors and the limited precision of floating-point numbers.
Now What?
If I can find any more constexpr
contortions, I’ll definitely be making more posts. There’s still so much of the standard math library left, you know.