[Math] Numerically Efficient Approximation of cos(s)

approximationnumerical methodstrigonometry

I have an application where I need to run $\cos(s)$ (and $\operatorname{sinc}(s) = \sin(s)/s$) a large number of times and is measured to be a bottleneck in my application.

I don't need every last digit of accuracy: $10^{-10}$ over an input range of $[-15^\circ < s < 15^{\circ}]$ should be sufficient (This is the limit of the input sensor data).

I have implemented a simple Taylor approximation, but would like to ask:

  • (a) Is there a more efficient approximation?
  • (b) If Taylor is the most efficient, is there a better way to implement it?

    // Equation Coefficients
    double s_2  = 0.25 * omega_ebe.squaredNorm();
    double s_4  = s_2 * s_2;
    double s_6  = s_4 * s_2;
    double s_8  = s_6 * s_2;
    double s_10 = s_8 * s_2;
    
    double cos_coef  = 1.0 - s_2 /  2.0 + s_4 /  24.0 - s_6 /   720.0 + s_8 /  40320.0 - s_10 /  3628800.0;
    double sinc_coef = 0.5 - s_2 / 12.0 + s_4 / 240.0 - s_6 / 10080.0 + s_8 / 725760.0 - s_10 / 79833600.0;
    

EDIT: I haven't forgotten to select an answer! I'm going to code a few of the up and run them on target (an embedded PowerPC and an embedded ARM) to see how they perform.

Best Answer

You can improve on the Taylor/Padé idea. What you can do is scale your argument by repeated halving; say, $z^\prime=\dfrac{z}{2^n}$, where $n$ is chosen such that the error term in either of the Taylor or Padé approximants is tiny enough. Having done so, you can iterate the double-angle identity (that is, $\cos\,2z=2\cos^2 z-1$) $n$ times on the result of the Taylor or Padé approximant evaluated at $z^\prime$. The result is the cosine value you need.

If you need a sine value as well, you will have to couple the double angle identity for the cosine with the double angle identity for the sine, $\sin\,2z=2\sin\,z\cos\,z$.


A Mathematica demonstration:

testCos[z_?InexactNumberQ] := Module[{co, za = z, k = 0},
  (* repeated halving *)
  While[Abs[za] > 1/4, za /= 2; k++];
  (* Padé approximant *)
  co = (1 + za^2*(-115/252 + (313*za^2)/15120))/(
        1 + za^2*(11/252 + (13*za^2)/15120));
  (* double-angle identity *)
  Do[co = 2 co^2 - 1, {k}];
  co]

Plot[testCos[z] - Cos[z], {z, -Pi, Pi}, PlotRange -> All]

comparison of "true" cosine and cosine by repeated doubling

The Abs[za] > 1/4 criterion was determined by plotting the Padé approximant and determining the range where the difference between it and the cosine was "small enough". You will have to experiment with your particular application.

fedja's advice in the comments should be emphasized quite a bit: whether you take the Taylor or Padé route, it pays to put your polynomial or rational function in Horner form. Less effort is needed in the evaluation, and it can be more accurate than the expanded polynomial or rational function.

Related Question