글
아래는 아크탄젠트를 계산하기 위한 다양한 방법을 기술하고 있다.
아크탄젠트 연산은(삼각함수의 역함수 포함) 양변의 길이를 이용하여 각도값을 알고자 하는 경우에 사용한다.
아크 탄젠트는 라디안을 반환하므로 각도는 다음의 식으로 부터 구해질 수 있다
deg=atan(x)*180/pi()의 수식으로 구해진다
하드웨어상에서 이를 연산하기 윗한 수치해석적인 Arctangent의 연산 방법은 여러가지가 있으나 아래 문서(http://www.convict.lu/Jeunes/Math/arctan.htm)에 따르면 Look-up Table방식과 Polynomial 연산에 기반한 경우가 가장 정밀도가 높다고 한다.
Arctan(x) using CORDIC
1. The problem and some solutions
Sometimes, especially when navigating a robot, you'll have to determine a certain angle from the tangens-value. This is challenging because the RCX only works with small integer variables within the limits of -32768 and 32767. The arctan-function has to cover the range of 0...45 degrees e.a values of x between 0 and 1, first to avoid overflow errors or a division by zero problem at angles around 90 degrees. If the arctan has to be found for an angle alpha between 45 and 90 degrees (e.a. values of x above 1), you'll search for the arctan(1/x) and extract alpha=90-arctan(1/x).
There are several possibilities to do the job, such as a look-up table, a linear or polynomial approximation or even a CORDIC algorithm. As you can see in the picture above, linear approximation will produce some big errors (~5°). Cumulated with other error sources, this will probably spoil your result.
An acceptable result is given by polynomial approximation. The least squares technique gives the formula:
y = -0.30097 + 0.61955*x - 0.001659*x*x
x :number from 0..100%
y : angle from 0..45° as approximation of the atan function.
This formula has to be simplified for the RCX-use to always stay in the small-integer limits:
y ={ [-150 + 310 * x - (x*x DIV 2) - (x*x DIV 3) ] DIV 50 + 5 } DIV 10
{Here the RCX-Code}
#define(x,1) setvar(x^2,var,x) |
This polynomial approximation gathers all the advantages: high speed (0.1 seconds), low memory, good accuracy!
We also tried a tricky approximation of This subroutine has been incorporated in both the second compass-sensor design and the pathfinder.
Here the integral algorithm programmed first in PASCAL, then in RCX-Code.
{dx of the integral is set equal to 1} {the tangens must be entered in x as a percent-value e.a. a value between 0 and 100} FUNCTION arc_tan(x : smallint) : smallint; |
{small integer variables: x, y ,f_y ,i, result }
{the tangens must be entered in x as a percent-value e.a. a value between 0 and 100} beginofsub(arctan) divvar(y,con,10) {div 100, rounded} sumvar(y,con,100) {we take f(y)=10000/(100+y*y/100)} {the angle is to be read out of result} |
This is quite an interesting program-design. It does not affect much memory and has a precision of 1°, which is sufficient with the PEWATRON-sensor. But how about speed? In fact there are two major inconveniences that appear: first, the computing time is proportional to the input-value x, second, the RCX beeing a rather slow computer, it needs 3,6 seconds to calculate the arctan(1), which is quite a lot. In the case of the PEWATRON compass-programming this doesn't matter, because the sensor's recover-time stays around 2 seconds after turning, but in the case of the pathfinder this makes the robot react with an unstable delay. The actual course is never the real instant course but the course of some tenth of a second or even more in the past.
This delay-problem can be surrounded by a look-up table, where only few instructions have to be done.
Here an example of a look-up table. This program is not elegant, but it is very quick: 45° need only 0.2 seconds.
{x is a value from 0..100%} BeginOfSub(Arctan) IF(var,x,LT,con,2) setvar(result,con,0) ELSE() IF(var,x,LT,con,4) setvar(result,con,1) ELSE() IF(var,x,LT,con,6) setvar(result,con,2) ELSE() IF(var,x,LT,con,8) setvar(result,con,3) ELSE() etc..etc..etc... ENDIF() ENDIF() ENDIF() ENDIF() EndOfSub() |
2. The CORDIC solution
CORDIC (COordinate Rotation DIgital Computer) is an iterative algorithm for calculating trigonometric functions and has been developed by J.E. Volder in 1959 (see "CORDIC Trigonometric Computing Technique", IRE Transactions on Electronic Computers, EC-8, Sept. 1959). It calculates the trig and even hyperbolic functions to any desired precision. The central idea is to rotate the phase of a complex number by a succession of constant values. Yes you should have paid attention in maths!
Given a complex value Z with the real component a, the imaginary component b, the phase phi:
Z = a + bj | tan(phi) = b / a, a<>0 | if a=0 then (if b>0 phi = 90° else phi = - 90°) |
magnitude(Z) = SQRT(a^2 + b^2) | cos(phi) = a / m(Z) | sin(phi) = b / m(Z) |
Given a second complex value W = c + dj
Z . W = (a.c - b.d) + (a.d + b.c) j
Remember that when you multiply a pair of complex numbers, their phases are added and their magnitudes multiply. Similary, when you multiply a complex number by the conjugate of the other, the phase of the conjugated one is substracted, but the magnitudes still multiply. The conjugate of Z, Z* = a - bj.
To rotate by +90°, multiply by R = j -->Z' = -b + aj. To rotate by -90°, multiply by R = -j --> Z' = b - aj |
To rotate by an angle less than 90°, the CORDIC idea asks to multiply by numbers of the form R = 1 + kj, where k will be decreasing powers of 2, starting with k = +/-2^0 = +/- 1.0 So k will successively be equal to +/-1.0, 0.5, 0.25, 0.125, etc. Generally k = +/- 2^-L, where L designates the power of two: 0, 1, 2, 3 ... The phase of R = atan(k). Rotating will be equivalent to multiplying Z' = Z.R = (a+bj).(1+kj) = (a-b.k) + (a.k+b)j. |
For better comprehension have a look at the table below. You'll see that every rotation causes the magnitude to grow. The factor is dependent on L. If successive rotations are completed with L increasing progressively, the relative gain to the initial magnitude (called the CORDIC gain) grows, but tends to a limit value of 1.6467...
L | k=2^L | R=1 + kj | Phase of R in ° | m(R) | CORDIC Gain |
0 | 1.0 | 1+1.0j | 45.00000 | 1.41421356 | 1.41421356 |
1 | 0.5 | 1+0.5j | 26.56505 | 1.11803399 | 1.58113883 |
2 | 0.25 | 1+0.25j | 14.03624 | 1.03077641 | 1.62980060 |
3 | 0.125 | 1+0.125j | 7.12502 | 1.00778222 | 1.64244841 |
4 | 0.0625 | 1+0.0625j | 3.57633 | 1.00195122 | 1.64568892 |
5 | 0.03125 | 1+0.03125j | 1.78991 | 1.00048816 | 1.64649228 |
6 | 0.015625 | 1+0.015625j | 0.89517 | 1.00012206 | 1.64669325 |
7 | 0.007813 | 1+0.007813j | 0.44761 | 1.00003052 | 1.64674351 |
... | ... | ... | ... | ... | ... |
Exercise 1
We know the complex number Z = a + bj, for example Z0 = 2 + 7j. Determine the phase phi.
CORDIC Solution:
This exercise is equivalent to fixing the arctan(2/7).
1. The CORDIC algorithm first lets you rotate by +90° if the imaginary argument b<0 and by -90° if the b>0. Example: Z1 = 7 - 2j
2. Then rotate successively by atan(k), where k=+/-2^-L, L=0, 1, 2, 3, 4,.... . The decision of sign is always taken on the sign of the imaginary argument b. Obviously phii will tend towards 0. And this is the trick: we only have to add the well known values (which we poll from a look-up table) of the atan(ki). At every computation these numbers will have the same absolute values. What only will change from case to case is the actual sign of ki. If the actual phase of the rotated vector has sensibly reached 0, the opposite of sum=+/-90+S(atan(ki)) gives us the phase of the initial complex number! ATTENTION: for this whole operation the real argument a must always be positive, e. a. angle values from -90 to +90°!!!
For better understanding have a look at the table below, or better try to calculate it by your own:
i | L | real arg. ai | imag. arg.bi | bi>0? --> sign | ki | atan(ki) in ° | "+/- 90 + S(atan(ki)) |
1 | 2 | 7 | -1 | -90 | |||
2 | 0 | 7 | -2 | 1 | 1 | 45 | -45 |
3 | 1 | 9 | 5 | -1 | -0,5 | -26,5650512 | -71,56505118 |
4 | 2 | 11,5 | 0,5 | -1 | -0,25 | -14,0362435 | -85,60129465 |
5 | 3 | 11,625 | -2,375 | 1 | 0,125 | 7,12501635 | -78,4762783 |
6 | 4 | 11,921875 | -0,921875 | 1 | 0,0625 | 3,57633437 | -74,89994392 |
7 | 5 | 11,97949219 | -0,17675781 | 1 | 0,03125 | 1,78991061 | -73,11003331 |
8 | 6 | 11,98501587 | 0,19760132 | -1 | -0,015625 | -0,89517371 | -74,00520702 |
9 | 7 | 11,98810339 | 0,01033545 | -1 | -0,0078125 | -0,44761417 | -74,45282119 |
10 | 8 | 11,98818414 | -0,08332161 | 1 | 0,00390625 | 0,2238105 | -74,22901069 |
11 | 9 | 11,98850961 | -0,03649277 | 1 | 0,00195313 | 0,11190568 | -74,11710502 |
12 | 10 | 11,98858089 | -0,01307771 | 1 | 0,00097656 | 0,05595289 | -74,06115212 |
13 | 11 | 11,98859366 | -0,00137011 | 1 | 0,00048828 | 0,02797645 | -74,03317567 |
14 | 12 | 11,98859433 | 0,00448369 | -1 | -0,00024414 | -0,01398823 | -74,0471639 |
15 | 13 | 11,98859542 | 0,00155679 | -1 | -0,00012207 | -0,00699411 | -74,05415801 |
verific. | tan(j) = bi/ai | atan(j) | |||||
3,5 | 74,0546041 |
Download the Excel sheet
3. This computation gives us also the magnitude through the formula:
m(Z) = au
This formula may be reduced to:
m(Z) = au / 1.6467, as shown above.
An RCX-program for this algorithm looks like:
{this is a CORDIC iteration for the arctan function} {...local variables F*X..} #define(p,20) #define(q,21) #define(r,22) #define(h,23) {...argument variables...} #define(F,10) #define(X,11) #define(res,12) {...main variables...} #define(value1,1) #define(value2,2) #define(s,3) #define(k,4) #define(phase,5) #define(sum_phase,6) #define(temp,7) #define(L,8) #define(time,9) {for test use} {...subroutines} #define(F*X,1) beginoftask(main) cleartimer(timer_1) setvar(value1,con,260) {test values, may be middle-adjusted raw-values of compass-sensor} setvar(value2,con,50) {+/- 90° rotation} sgnvar(s,var,value2) {sign decision} mulvar(s,con,-1) {has to be the opposite} setvar(sum_phase,con,900) {reset} mulvar(sum_phase,var,s) setvar(temp,var,value1) setvar(value1,var,s) mulvar(value1,con,-1) mulvar(value1,var,value2) setvar(value2,var,temp) mulvar(value2,var,s) {iteration } setvar(L,con,0) while(var,L,LT,con,9) sumvar(L,con,1) {index L} if(var,L,eq,con,1) {look-up table} setvar(k,con,10000) setvar(phase,con,450) endif() if(var,L,eq,con,2) {look-up table} setvar(k,con,5000) setvar(phase,con,266) endif() if(var,L,eq,con,3) {look-up table} setvar(k,con,2500) setvar(phase,con,140) endif() if(var,L,eq,con,4) {look-up table} setvar(k,con,1250) setvar(phase,con,71) endif() if(var,L,eq,con,5) {look-up table} setvar(k,con,625) setvar(phase,con,36) endif() if(var,L,eq,con,6) {look-up table} setvar(k,con,313) setvar(phase,con,18) endif() if(var,L,eq,con,7) {look-up table} setvar(k,con,156) setvar(phase,con,9) endif() if(var,L,eq,con,8) {look-up table} setvar(k,con,78) setvar(phase,con,4) endif() sgnvar(s,var,value2) {sign decision} mulvar(s,con,-1) {opposite sign} mulvar(k,var,s) mulvar(phase,var,s) sumvar(sum_phase,var,phase) {here ten times the opposite of the arctan value will be found} setvar(temp,var,value1) setvar(F,var,k) setvar(x,var,value2) gosub(F*X) subvar(value1,var,res) setvar(x,var,temp) gosub(F*X) sumvar(value2,var,res) endwhile() setvar(time,timer,timer_1) endoftask() {============================================} { this subroutine multiplies an integer value x from -1638 to +1638 with a floating variable F (without point) of the form p. ex. 10448 which means 1.0448 The range of F must be: -19999 19999} beginofsub(F*X) {.....le'ts take x=909 as an example.....} setvar(q,var,F) {10448} divvar(q,con,1000) {10448 div 1000=10} setvar(res,var,q) {10} mulvar(res,var,x) {10*909=9090} mulvar(q,con,-1000) {10*(-1000)=-10000} sumvar(q,var,F) {-10000+10448=448} setvar(r,var,q) {448} divvar(q,con,100) {448 div 100=4} setvar(h,var,q) {4} mulvar(h,var,x) {4*909=3636} mulvar(q,con,-100) {4*100=-400} sumvar(r,var,q) {448-400=48} setvar(q,var,r) {48} divvar(q,con,10) {48 div 10=4} setvar(p,var,q) {4} mulvar(p,var,x) {4*909=3636} divvar(p,con,10) {3636 div 10=363} sumvar(h,var,p) {3636+363=3999} mulvar(q,con,-10) {4*(-10)=-40} sumvar(r,var,q) {48-40=8} mulvar(r,var,x) {8*909=7272} divvar(r,con,100) {7272 div 100=72} sumvar(h,var,r) {3999+72=4071} divvar(h,con,10) {4071 div 10=407} sumvar(res,var,h) {9090+407=9497} sumvar(res,con,5) {9497+5=9503} divvar(res,con,10) {9503 div 10=950} endofsub() |
Using this solution, we have a rather disappointing result: duration of a computation: 2.2 seconds; accuracy: 1-2°; high memory use. But we have learned some exciting program-technique!
Exercise 2 : Calculate the square-root of a number x using CORDIC.
CONCLUSION
The best solution are the look-up table and the polynomial approximation.
RECENT COMMENT