Announcement

Collapse
No announcement yet.

ATN2 function (i.e. de-ambiguized ATAN)?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • ATN2 function (i.e. de-ambiguized ATAN)?

    With all my astronomical calculations of late I stumbled over
    a beloved problem from my early days in trig class: When taking
    the ATAN(x/y) you need to check x and y to find out in which quadrant
    the result must come to be.

    Has anyone ever built a nice ASM function that would be fast, mean and lean?
    This is my implementation (which works, but is kind of clumsy)... or could
    I get Tom or Lance to put the ATAN2 function on the wishlist for the compiler?

    Code:
    'serves to find the correct quadrant when doing an atan(y/x).
    'returns raqdians
    Function ATN2 (Y As Ext, X As Ext) As Ext
      Dim res As Ext
    
      If (X = 0) Then
        If (Y = 0) Then
           res = 0
        Else
           res = Sgn(Y) * (Pi/2)
        End If
      Else
        res = Atn(Y / X)
        If (X < 0) Then
           If (Y < 0) Then
              res = res - Pi
           Else
              res = res + Pi
           End If
        End If
      End If
      Function = res
    End Function

    ------------------
    Balt
    bi at inside dot net
    "It's not the end, not even the beginning of the end, but, it might be the end of the beginning!"



    [This message has been edited by Balthasar Indermuehle (edited August 19, 2003).]

  • #2
    Balt,
    the following ASM code snippet appears to match your ATN2() function. Give it a try and if it doesn't work, let me know and I'll look into it further.

    It uses the FPU's built in ATAN function.

    Code:
    !fld x##    ;get x onto the FPU stack
    !fld y##    ;get y onto the FPU stack
    !fpatan     ;get the ATAN(x/y), put result in ST(1) then pop the stack
    !fstp z##   ;store the result in z## (or wherever you want it) and pop the stack
    Paul.

    Comment


    • #3
      here is my non-asm version, which is in this link:
      http://www.powerbasic.com/support/pb...ad.php?t=23341
      single precision is faster, but it may not be sufficiently
      precise for your purpose. (ez ~ y).
      Code:
      function calculatephi(ex as single, ez as single) as single
          ' these corrections are needed to make phi take a full
          ' circle i.e. 360 degrees.
          ' without the corrections phi will only vary 180 degrees
          ' (-90 to 90) corresponding to the range for arcus tangens (atn).
          local ph as single
          if abs(ex) < ez*1e-36! then ex = ez*1e-36!
          ph = atn(ez/ex)
          if ez/ex < 0! then
              ph=ph+3.141593! : if ez < 0! then ph=ph+3.141593!
          else
              if ez < 0! then ph=ph+3.141593!
          end if
          function = ph
      end function



      [this message has been edited by erik christensen (edited august 19, 2003).]

      Comment


      • #4
        -

        ------------------

        Comment


        • #5
          Eric, your code seems to work 50% of the time, the other two quadrants are mixed up on first glance. Is that possible at all or have you double and triple checked it?

          Paul's code does not appear to work (it always returns 45 degs,
          maybe my way of implementing this ASM is not valid?).

          Paul, please let me know if you find a solution please. Your way seemed so nice!
          Here's copy paste code to test:

          Code:
          #Compile Exe "atn2test.exe"
          
          Macro Pi = 3.141592653589793##
          Macro Rad2Deg(dpRadians) = (dpRadians*57.29577951308232##)
          
          Function ATN2 (Y As Ext, X As Ext) As Ext
            #Register None
            Dim res As Ext
          
            !fld x##    ;Get x onto the FPU stack
            !fld y##    ;Get y onto the FPU stack
            !fpatan     ;Get the ATAN(x/y), Put result In ST(1) Then pop the stack
            !fstp res##   ;store the result In res## And pop the stack
            Function = res  
          End Function
          
          
          Function PbMain
            'testing the mighty atn2 function
            Dim x As Ext
            Dim y As Ext
            
            x = -0.1:  y = -0.1:  MsgBox "Should be 225: " + Format$(Rad2Deg(Atn2(y, x)))
            x = -0.1:  y = 0.1:  MsgBox "Should be 135: " + Format$(Rad2Deg(Atn2(y, x)))
            x = 0.1:  y = 0.1:  MsgBox "Should be 45: " + Format$(Rad2Deg(Atn2(y, x)))
            x = 0.1:  y = -0.1:  MsgBox "Should be 315: " + Format$(Rad2Deg(Atn2(y, x)))
            
          End Function
          ------------------
          Balt
          bi at inside dot net
          "It's not the end, not even the beginning of the end, but, it might be the end of the beginning!"



          [This message has been edited by Balthasar Indermuehle (edited August 19, 2003).]

          Comment


          • #6
            Balt,
            2 problems.
            1) when using variables directly in ASM statements, you can't use the parameter of a function directly if it's passed byref.
            You need to pass x## and y## byval (the easy way) or calculate where the x## and y## are stored from the pointer passed byref to the subroutine (harder way).


            2) Somewhere along the line the order that X and Y were pushed onto the FPU stack got swapped!
            Push Y first, then X.


            3) I know I said 2 but this isn't a problem really.
            The FPU instruction always returns a value with a size less than PI. So, it gives -180 to +180 degrees rather than 0 to 360 degrees.

            Try the following, which has the 2 corrections mentioned above.

            Paul.

            Code:
            #COMPILE EXE "atn2test.exe"
            
            MACRO Pi = 3.141592653589793##
            MACRO Rad2Deg(dpRadians) = (dpRadians*57.29577951308232##)
            
            FUNCTION ATN2 (BYVAL Y AS EXT,BYVAL X AS EXT) AS EXT
              #REGISTER NONE
              DIM res AS EXT
            
              !fld y##    ;Get y onto the FPU stack
              !fld x##    ;Get x onto the FPU stack
              !fpatan     ;Get the ATAN(y/x), Put result In ST(1) Then pop the stack
              !fstp res##   ;store the result In res## And pop the stack
            
              FUNCTION = res
            END FUNCTION
            
            
            FUNCTION PBMAIN
              'testing the mighty atn2 function
              DIM x AS EXT
              DIM y AS EXT
            
              x = -0.1:  y = -0.1:  MSGBOX "Should be 225: " + FORMAT$(Rad2Deg(Atn2(y, x)))
              x = -0.1:  y = 0.1:  MSGBOX "Should be 135: " + FORMAT$(Rad2Deg(Atn2(y, x)))
              x = 0.1:  y = 0.1:  MSGBOX "Should be 45: " + FORMAT$(Rad2Deg(Atn2(y, x)))
              x = 0.1:  y = -0.1:  MSGBOX "Should be 315: " + FORMAT$(Rad2Deg(Atn2(y, x)))
            
            END FUNCTION

            [This message has been edited by Paul Dixon (edited August 19, 2003).]

            Comment


            • #7
              Paul, cool! This works fine for +-pi

              Since I need 0-360, I simply add 2*pi for negative values:
              Code:
              If res < 0 Then res = res + 2*Pi
              The PB compiler makes this into:
              Code:
              004010B9   D9EE             FLDZ
              004010BB   DBAD 70FFFFFF    FLD TBYTE PTR SS:[EBP-90]
              004010C1   DED9             FCOMPP
              004010C3   DFE0             FSTSW AX
              004010C5   9E               SAHF
              004010C6   73 1A            JNB SHORT ATN2TEST.004010E2
              004010C8   DB2D A8334000    FLD TBYTE PTR DS:[4033A8]
              004010CE   DE0D A4334000    FIMUL WORD PTR DS:[4033A4]
              004010D4   DBAD 70FFFFFF    FLD TBYTE PTR SS:[EBP-90]
              004010DA   DEC1             FADDP ST(1),ST
              004010DC   DBBD 70FFFFFF    FSTP TBYTE PTR SS:[EBP-90]
              Can you make it shorter than this in ASM?




              ------------------
              Balt
              bi at inside dot net
              "It's not the end, not even the beginning of the end, but, it might be the end of the beginning!"

              Comment


              • #8
                Balt,
                <<Can you make it shorter than this in ASM?>>

                Of course! Since you appear to be after the fastest possible code, I've made a few changes.

                1)I know I said to pass the parameters byval, because it's easier. But it is a little slower and, since you appear to be after saving every clock cycle, I've gone back to passing byref.
                This means we have to look up the X and Y from the pointers provided (2 extra opcodes) but it saves PB from having to create a temporary variable to store the ByVal parameters in so overall it is a little quicker this way.

                2) Storing the result from the FPU directly in the FUNCTION at the end saves needing RES so we save using an uneccesary variable.

                3) The adding of 2pi is shortened to 8 short instructions without memory references so there's less of them and they should be quicker

                4) I've added more comments so you can see what's going on.

                Paul
                Code:
                #COMPILE EXE "atn2test.exe"
                
                MACRO Pi = 3.141592653589793##
                MACRO Rad2Deg(dpRadians) = (dpRadians*57.29577951308232##)
                
                FUNCTION ATN2 ( Y AS EXT, X AS EXT) AS EXT
                  #REGISTER NONE
                 ' DIM res AS EXT
                
                '!fld y##               ;Get y onto the FPU stack (for parameters passed ByVal)
                !mov eax,y##            ;for ByRef (a little quicker)get the pointer to Y##
                !fld tbyte ptr [eax]    ;load the extended value pointed to by eax
                
                
                '!fld x##               ;Get x onto the FPU stack (for parameters passed ByVal)
                !mov eax,x##            ;for ByRef (a little quicker)get the pointer to x##
                !fld tbyte ptr [eax]    ;load the extended value pointed to by eax
                
                !fpatan                 ;Get the ATAN(y/x), Put result In ST(1) Then pop the stack.
                                        'i.e. result is now on top of stack in st(0)
                 
                !ftst                   ;test ST(0) (that's the top of stack) against zero
                !fstsw ax               ;get the result into AX
                !sahf                   ;get the FPU flags into the CPU flags
                !jae short skip         ;if above or equal (i.e.value is not -ve) then skip the Add 2*pi code
                
                !fldpi                  ;get pi
                !fadd st(1),st(0)       ;add pi to result
                !fadd                   ;and again, for 2pi then pop the now unneeded pi from st(0)
                                        'this leaves the result +2pi on the stack at st(0) if it was -ve
                 skip:
                   
                !fstp function          ;store the result directly in the function return value and pop the stack
                
                 ' FUNCTION = res 'not needed since it's stored directly int the function return value now
                 
                END FUNCTION
                
                
                FUNCTION PBMAIN
                  'testing the mighty atn2 function
                  DIM x AS EXT
                  DIM y AS EXT
                
                  x = -0.1:  y = -0.1:  MSGBOX "Should be 225: " + FORMAT$(Rad2Deg(Atn2(y, x)))
                  x = -0.1:  y = 0.1:  MSGBOX "Should be 135: " + FORMAT$(Rad2Deg(Atn2(y, x)))
                  x = 0.1:  y = 0.1:  MSGBOX "Should be 45: " + FORMAT$(Rad2Deg(Atn2(y, x)))
                  x = 0.1:  y = -0.1:  MSGBOX "Should be 315: " + FORMAT$(Rad2Deg(Atn2(y, x)))
                
                END FUNCTION
                ------------------
                Editted to add the word SHORT in the ASM jump.
                This saves 4 bytes over the long jump which was compiled by default.
                This was an oversight due to a change from PB/CC1.0 which always defaulted to a short jump.



                [This message has been edited by Paul Dixon (edited August 19, 2003).]

                Comment


                • #9
                  Ultracool! Works like a charm, thanks!

                  Is there a complete mnemonics helpfile or website apart from
                  the 3 volume intel books?

                  Cheers

                  ------------------
                  Balt
                  bi at inside dot net
                  "It's not the end, not even the beginning of the end, but, it might be the end of the beginning!"

                  Comment


                  • #10
                    Balt,
                    note the small edit in the above code.

                    I just use the Intel Books but I'm sure there are web sites out there with useful information. Try www.masmforum.com , I think they have tutorials over there. If not, they'll know the best places to look for more information.

                    Paul.

                    Comment


                    • #11
                      Ok, seen the change, thanks!

                      ------------------
                      Balt
                      bi at inside dot net
                      "It's not the end, not even the beginning of the end, but, it might be the end of the beginning!"

                      Comment


                      • #12
                        Balt,

                        I think my function works OK - try the following test program.
                        Code:
                        #COMPILE EXE
                        #REGISTER NONE
                        #DIM ALL
                        
                        FUNCTION CalculatePhi(EX AS SINGLE, EZ AS SINGLE) AS SINGLE
                            ' These corrections are needed to make Phi take a full
                            ' circle i.e. 360 degrees.
                            ' Without the corrections Phi will only vary 180 degrees
                            ' (-90 to 90) corresponding to the range for arcus tangens (ATN).
                            LOCAL Ph AS SINGLE
                            IF ABS(EX) < EZ*1E-36! THEN EX = EZ*1E-36!
                            Ph = ATN(EZ/EX)
                            IF EZ/EX < 0! THEN
                                Ph=Ph+3.141593! : IF EZ < 0! THEN Ph=Ph+3.141593!
                            ELSE
                                IF EZ < 0! THEN Ph=Ph+3.141593!
                            END IF
                            FUNCTION = Ph
                        END FUNCTION
                        
                        FUNCTION PBMAIN
                            LOCAL Result$,A!,t$,X!,Y!,i1&,i!
                            FOR i = 0 TO 8*ATN(1) STEP ATN(1)*0.5
                                X=COS(i) : Y=SIN(i)
                                A! = CalculatePhi(X, Y)
                                t=t+"X="+FORMAT$(X,"##.#####")+"    Y="+FORMAT$(Y,"##.#####")+ _
                                "       true angle="+FORMAT$(i,"##.#####")+ _
                                "       angle from X and Y="+FORMAT$(A,"##.#####")+$CRLF
                            NEXT
                            MSGBOX t,,"Get angle from x and y"
                        END FUNCTION
                        ------------------


                        [This message has been edited by Erik Christensen (edited August 20, 2003).]

                        Comment


                        • #13
                          Erik

                          your function swaps EX and EZ with the Y/X of the other functions. Other than that it works just as well it seems.

                          Cheers

                          ------------------
                          Balt
                          bi at inside dot net
                          "It's not the end, not even the beginning of the end, but, it might be the end of the beginning!"

                          [This message has been edited by Balthasar Indermuehle (edited August 20, 2003).]

                          Comment


                          • #14
                            I have simplified the function further and transformed it to
                            an extended precision function. Now it uses X and Y to simplify
                            matters. This version of the test program works with degrees.
                            Code:
                            #COMPILE EXE
                            #REGISTER NONE
                            #DIM ALL
                            
                            FUNCTION CalculatePhi(X AS EXT, Y AS EXT) AS EXT
                                ' These corrections are needed to make Phi take a full
                                ' circle i.e. 360 degrees.
                                ' Without the corrections Phi will only vary 180 degrees
                                ' (-90 to 90) corresponding to the range for arcus tangens (ATN).
                                LOCAL Ph AS EXT
                                IF ABS(X) < 1E-1000## THEN X = 1E-1000##
                                Ph = ATN(Y/X)
                                IF Ph < 0## THEN Ph = Ph + 3.14159265358979324##
                                IF Y  < 0## THEN Ph = Ph + 3.14159265358979324##
                                FUNCTION = Ph
                            END FUNCTION
                            
                            FUNCTION PBMAIN
                                LOCAL A##,t$,X##,Y##,i##
                                FOR i = 0## TO 7.9##*ATN(1) STEP ATN(1)*0.5##
                                    X=COS(i) : Y=SIN(i)
                                    A = CalculatePhi(X, Y)
                                    t=t+"X="+FORMAT$(X,"##.####")+"    Y="+FORMAT$(Y,"##.####")+ _
                                    "       true angle="+FORMAT$(i*57.29577951308232##,"###.######")+ _
                                    "       angle from X and Y="+FORMAT$(A*57.29577951308232##,"###.######")+$CRLF
                                NEXT
                                MSGBOX t,,"Get angle in DEGREES from X and Y"
                            END FUNCTION
                            Later: Function further simplified. Now I think it would not
                            be much slower than the ASM function.



                            [This message has been edited by Erik Christensen (edited August 20, 2003).]

                            Comment


                            • #15
                              Erik,

                              If Y = 0 your code fails.

                              Adding the line in red solves the case:
                              Code:
                              FUNCTION CalculatePhi(X AS EXT, Y AS EXT) AS EXT
                                  ' These corrections are needed to make Phi take a full
                                  ' circle i.e. 360 degrees.
                                  ' Without the corrections Phi will only vary 180 degrees
                                  ' (-90 to 90) corresponding to the range for arcus tangens (ATN).
                                  LOCAL Ph AS EXT
                                  IF ABS(X) < 1E-1000## THEN X = 1E-1000##
                                  Ph = ATN(Y/X)
                                  IF Ph < 0## THEN Ph = Ph + 3.14159265358979324##
                                  IF Y  < 0## THEN Ph = Ph + 3.14159265358979324##
                                  [COLOR="Red"]IF Y = 0 AND X < 0 THEN Ph = Ph + 3.14159265358979324##[/COLOR]
                                  FUNCTION = Ph
                              END FUNCTION
                              "The trouble with quotes on the Internet is that you can never know if they are genuine." - Abraham Lincoln.

                              Comment


                              • #16
                                I was just looking for a cleaner method without using ASM and found this formula on Wikipedia. Some of the tests above don't test for X or Y having a zero value.

                                Code:
                                #Compile Exe "ATAN2_Test.exe"
                                
                                MACRO Degrees(Radians) = (Radians*57.29577951308232##)
                                
                                Function PbMain
                                  'testing the mighty atn2 function
                                  Dim x As Ext
                                  Dim y As Ext
                                  
                                  Global pi As Ext
                                  
                                  pi = 4*Atn(1)
                                  
                                	'Test My Atan2
                                  x = -1: y = -1: MsgBox "Should be 225: " + Format$(Degrees(Atan2(y, x)))
                                  x = -1: y = 1:  MsgBox "Should be 135: " + Format$(Degrees(Atan2(y, x)))
                                  x = 1:  y = 1:  MsgBox "Should be 45: " + Format$(Degrees(Atan2(y, x)))
                                  x = 1:  y = -1: MsgBox "Should be 315: " + Format$(Degrees(Atan2(y, x)))
                                  x = 0:  y = -1: MsgBox "Should be 270: " + Format$(Degrees(ATAN2(y, x)))
                                  x = 0:  y = 0:  MsgBox "Should be 0: " + Format$(Degrees(ATAN2(y, x)))
                                  x = 0:  y = 1:  MsgBox "Should be 90: " + Format$(Degrees(ATAN2(y, x)))
                                  x = -1: y = 0:  MsgBox "Should be 180: " + Format$(Degrees(ATAN2(y, x)))
                                  x = 1:  y = 0:  MsgBox "Should be 0: " + Format$(Degrees(ATAN2(y, x)))
                                	
                                End Function
                                
                                
                                
                                Function ATAN2(ByVal Y As Ext, ByVal X As Ext) As Ext
                                	Local Radians As Ext
                                	
                                	Radians = 2*Atn((Sqr(X^2+Y^2)-X)/y)
                                	If Radians < 0 Then Radians = Radians+pi*2
                                	
                                	ATAN2 = Radians
                                End Function
                                Last edited by Jeffrey W Smith; 19 Nov 2010, 06:19 PM.

                                Comment


                                • #17
                                  Hi Jeffrey,

                                  There is not an arc when x and y are worth zero
                                  Code:
                                      'Test My Atan2
                                    DIM s AS STRING
                                    x = 1:  y = 0:  s =     "Should be   0: " + FORMAT$(Degrees(ATAN2(y, x))) & $CRLF
                                    x = 1:  y = 1:  s = s & "Should be  45: " + FORMAT$(Degrees(Atan2(y, x))) & $CRLF
                                    x = 0:  y = 1:  s = s & "Should be  90: " + FORMAT$(Degrees(ATAN2(y, x))) & $CRLF
                                    x = -1: y = 1:  s = s & "Should be 135: " + FORMAT$(Degrees(Atan2(y, x))) & $CRLF
                                    x = -1: y = 0:  s = s & "Should be 180: " + FORMAT$(Degrees(ATAN2(y, x))) & $CRLF
                                    x = -1: y = -1: s = s & "Should be 225: " + FORMAT$(Degrees(Atan2(y, x))) & $CRLF
                                    x = 0:  y = -1: s = s & "Should be 270: " + FORMAT$(Degrees(ATAN2(y, x))) & $CRLF
                                    x = 1:  y = -1: s = s & "Should be 315: " + FORMAT$(Degrees(Atan2(y, x))) & $CRLF
                                    x = 1:  y = 0:  s = s & "Should be   0: " + FORMAT$(Degrees(ATAN2(y, x))) : ? s
                                  Edited:
                                  - In the example, when x and y are worth exactly 1, these values are multiplied by the square root of 2 - as shown at 45, 135, 225 and 315 degrees. The correct is 0.707 .. in each one of then.
                                  - The calculation of the arc will be more efficient this way:
                                  Code:
                                      Radians = 2*ATN((SQR(X*X+Y*Y)-X)/Y)
                                  Last edited by Arthur Gomide; 20 Nov 2010, 06:30 AM.
                                  "The trouble with quotes on the Internet is that you can never know if they are genuine." - Abraham Lincoln.

                                  Comment


                                  • #18
                                    Jeffrey,
                                    without using ASM
                                    Why? ASM is by far the easiest way to do this calculation as the FPU already performs this calculation for you.
                                    If you avoid ASM then you just add considerable overhead to the calculation and the compiler will eventually use the same FPATAN opcode that you tried to avoid.

                                    Paul.

                                    Comment

                                    Working...
                                    X