Trigonometric functions are commonly used in real-time control applications, particularly within the inner loops of control algorithms, where speed and accuracy is essential. The performance of trigonometric functions is a key careabout for designers of these systems as it can have a significant impact on the overall performance of the system. Until recently, trignometric functions based on lookup tables were considered faster than the polynomial-based methods; however, with the inclusion of floating-point units (FPUs) and faster clock speeds, polynomial-based approximations have gained favor. TI has developed C functions of the most commonly used trigonometric functions using these polynomial-based methods and has optimized them for TI's Arm®-based microcontrollers (MCUs) and microprocessors (MPUs). This application note surveys the trigonometric functions that are available today and shares the optimization techniques used in these functions, as well as the results of our optimization efforts.
The TI-optimized trigonometric functions presented in this document can be found in MCU+ SDK v8.5 and later.
Arm® and Cortex® are registered trademarks of Arm Limited (or its subsidiaries) in the US and/or elsewhere.
All trademarks are the property of their respective owners.
Trigonometric functions are commonly used in real-time control applications, particularly within the inner loops of control algorithms, where speed and accuracy is essential. The performance of trigonometric functions is a key careabout for designers of these systems as it can have a significant impact on the overall performance of the system. Until recently, trignometric functions based on lookup tables were considered faster than the polynomial-based methods; however, with the inclusion of floating-point units (FPUs) and faster CPU clock speeds, polynomial-based approximations are gaining favor.
The most commonly used trigonometric functions are sine, cosine, arcsine, arccosine, arctangent, and atan2. Trigonometric optimization techniques for these functions fall into two categories:
These techniques are described in the following sections and the polynomial-based optimization techniques used by the TI Arm® Trigonometric Library are discussed.
Up until recently, lookup table-based approximations were considered faster than polynomial-based approximations. Lookup table-based approximations pre-compute N values of the target function and then index them into the table to select the two closest points and perform an interpolation between them. When using lookup table-based approximations, the accuracy can be adjusted by changing either the size of the lookup or the order of the interpolation function. However, this comes with the tradeoff of more memory usage and longer function times. This is the technique used by the Fast Math Functions in the Arm CMSIS Library where they use a table size of 512 entries to cover 0-2PI and then do a linear interpolation between the closest values.
With the inclusion of floating-point co-processors and faster clock speeds, the polynomial-based methods are now quite fast and have the added benefit of not requiring any table storage. The TI Arm Trig Library uses this method.
The first step in using polynomial approximations is to reduce the range of the input. The bigger the range needed to approximate, the higher order polynomial needed to achieve a specified level of accuracy, which means more computations and slower function performance.
The second step is to compute the approximation for the reduced range and then finally restore the range depending on which quadrant the input was originally in by using trigonometric identities.
There are a number of methods of finding the best polynomials to achieve the lowest error with the fewest terms, but the most commonly used is the minimax approximation algorithm and one version in particular: the Remez algorithm. The Remez algorithm is used to find a polynomial with the least maximum error. This polynomial is called a minimax polynomial. The minimax polynomial is what is used in our optimized trigonometric functions as it is ideal for control applications where worst-case performance is a key care-about.
This section shows how the above techniques are applied to optimize the computations for a Sin + Cos function. Sin + Cos functions are commonly used in the transforms for control algorithms where both sin and cos are needed at the same time.
The first step in using polynomial approximations is to range reduce the input so that the segment of the function that needs to be modeled is smaller. The most common range reduction for sin/cos computation is to reduce the input to the range.
Then, compute the approximation in this region and then adjust the sign depending on which quadrant the angle was in originally. Using the Chebyshev polynomials and the fact that sin(x) is an odd function and cos(x) is an even function, you will see the following:
If you assume that the input to the functions are limited to , you need to map the input value to the range before implementing the approximation. This can be done with a couple simple comparisons:
A further range reduction technique can be used to limit the input to using the trigonometric identities:
Which yields the following mapping for sin(x) and a similar one for cos(x):
Since you only have to model the sine cosine from , you need fewer coefficients to achieve higher accuracy.
Table 2-1 and Table 2-2 show the coefficients for the sine and cosine approximation obtained from the Sollya program. The table shows the error expected given the range reduction and order of the polynomial. If you are trying to achieve full single precision floating-point accuracy, then you need to get to ~1e-7.
Range | Number of Terms | Absolute Error | Polynomial |
---|---|---|---|
−π/2 : π/2 | 3 | 1.00E-04 |
Equation 8. x *
(0.999891757965087890625 + x2 *
(-0.165960013866424560546875 + x2 *
7.602870464324951171875e-3))
|
−π/2 : π/2 | 4 | 6.00E-07 |
Equation 22. x *
(0.999996483325958251953125 + x2 *
(-0.166647970676422119140625 + x^2 * (8.306086063385009765625e-3
+ x2 * (-1.83582305908203125e-4))))
|
−π/2 : π/2 | 5 | 6.00E-09 |
Equation 10. x * (1 +
x2 * (-0.1666665971279144287109375 +
x2 * (8.333069272339344024658203125e-3 +
x2 * (-1.98097783140838146209716796875e-4 +
x2 *
2.6061034077429212629795074462890625e-6))))
|
−π/4 : π/4 | 2 | 1.50E-04 |
Equation 11. x *
(0.99903142452239990234375 + x2 *
(-0.16034401953220367431640625))
|
−π/4 : π/4 | 3 | 5.60E-07 |
Equation 12. x *
(0.9999949932098388671875 + x2 *
(-0.166601598262786865234375 + x2 *
8.12153331935405731201171875e-3))
|
−π/4 : π/4 | 4 | 1.80E-09 |
Equation 13. x * (1 +
x2 * (-0.166666507720947265625 + x2 *
(8.331983350217342376708984375e-3 + x2 *
(-1.94961365195922553539276123046875e-4))))
|
−π/4 : π/4 | 5 | 6.00E-11 |
Equation 14. x * (1 +
x2 * (-0.16666667163372039794921875 +
x2 * (8.33337195217609405517578125e-3 +
x2 * (-1.98499110410921275615692138671875e-4 +
x2 *
2.800547008519060909748077392578125e-6))))
|
Range | Number of Terms | Absolute Error | Polynomial |
---|---|---|---|
−π/2 : π/2 | 3 | 6.00E-04 |
Equation 15. 0.9994032382965087890625 + x2 *
(-0.495580852031707763671875 + x2 *
3.679168224334716796875e-2)
|
−π/2 : π/2 | 4 | 6.70E-06 |
Equation 16. 0.99999332427978515625 + x2 *
(-0.4999125301837921142578125 + x2 *
(4.1487820446491241455078125e-2 + x2 *
(-1.27122621051967144012451171875e-3)))
|
−π/2 : π/2 | 5 | 6.00E-08 |
Equation 17. 0.999999940395355224609375 + x2 *
(-0.499998986721038818359375 + x2 *
(4.1663490235805511474609375e-2 + x2 *
(-1.385320327244699001312255859375e-3 + x2 *
2.31450176215730607509613037109375e-5)))
|
−π/4 : π/4 | 3 | 1.00E-05 |
Equation 18. 0.999990046024322509765625 + x2 *
(-0.4997082054615020751953125 + x2 *
4.03986163437366485595703125e-2)
|
−π/4 : π/4 | 4 | 3.30E-08 |
Equation 19. 1 + x2 *
(-0.49999892711639404296875 + x2 *
(4.16561998426914215087890625e-2 + x2 *
(-1.35968066751956939697265625e-3)))
|
−π/4 : π/4 | 5 | 1.00E-10 |
Equation 20. 1 + x2 *
(-0.5 + x2 * (4.16666455566883087158203125e-2 +
x2 * (-1.388731296174228191375732421875e-3 +
x2 *
2.4432971258647739887237548828125e-5)))
|
The arctangent function, and in particular, the arctangent2 function is a critical function in control applications. For example, in motor control, there may be sensors used to get the x and y position of a motor. Then, the application needs to translate that into an angular value. A standard arctan function would be called as arctan(y/x), but this loses the quadrant information as Q1 and Q3 are both positive and Q2 and Q4 are both negative. The arctan2 function accepts both the x and the y input to return a value in the full −π to π range versus the arctan function that returns values only in Q1 or Q4, −π/2 to π/2.
As the input to arctan(z) can be any number from −∞to ∞, use trig identities to reduce the range and get an approximation function that is relatively low complexity.
Start with:
These identities allow you to restrict the approximation range to abs(x) <=1.
The next step is to find a simple polynomial that approximates arctan in the range 0-1, or -1 to 1. Using sollya, try some different polynomial lengths to get the approximation error. This is shown in Table 2-3. You can see that the error is not falling that fast as a function of the number of terms in the polynomial, so even at 6 terms you are still at 3.4e-6 in the range (-1,1). Also note that arctan is an odd function where all the terms are odd powers.
Table 2-3 shows the coefficients for the arctangent approximation obtained from the Sollya program. The table shows the error expected given the range reduction and order of the polynomial.
Range | Terms | Abs err | Polynomial |
---|---|---|---|
-1 : 1 | 4 | 8.00E-05 |
Equation 21. x *
(0.99921381473541259765625 + x2 *
(-0.321175038814544677734375 + x2 *
(0.146264731884002685546875 + x2 *
(-3.8986742496490478515625e-2))))
|
-1 : 1 | 5 | 2.30E-05 |
Equation 22. x *
(0.999970018863677978515625 + x2 *
(-0.3317006528377532958984375 + x2 *
(0.1852150261402130126953125 + x2 *
(-9.1925732791423797607421875e-2 + x2 *
2.386303804814815521240234375e-2))))
|
-1 : 1 | 6 | 3.40E-06 |
Equation 23. x *
(0.999995648860931396484375 + x2 *
(-0.3329949676990509033203125 + x2 *
(0.19563795626163482666015625 + x2 *
(-0.121243648231029510498046875 + x2 *
(5.7481847703456878662109375e-2 + x2 *
(-1.3482107780873775482177734375e-2))))))
|
tan(pi/12) | 3 | 2.00E-07 |
Equation 24. x *
(0.999994814395904541015625 + x2 *
(-0.3327477872371673583984375 + x2 *
0.18327605724334716796875))
|
tan(pi/12) | 4 | 3.00E-09 |
Equation 25. x *
(0.999999940395355224609375 + x2 *
(-0.333319008350372314453125 + x2 *
(0.19920165836811065673828125 + x2 *
(-0.12685041129589080810546875))))
|
tan(pi/12) | 5 | 8.70E-11 |
Equation 26. x * (1 +
x2 * (-0.333333194255828857421875 + x2
* (0.19998063147068023681640625 + x2 *
(-0.14202083647251129150390625 + x2 *
9.6703059971332550048828125e-2))))
|
Math.h includes the standard functions for single-precision floating-point sinf(), cosf(), atanf(), and atan2f(), as well as the double-precision sin(), cos(), atan(), atan2(). These functions use polynomial-based approximation methods and provide high accuracy with no input limitations, but at the cost of more cycles. These functions are benchmarked and the results are shared at the end of this section.