Inverse trigonometric tangent

The category should probably read Documentation.

I like brief in doco but sometimes it will bite you. This should be an issue
but I am unsure how to word it properly. So I just put it down here while it
is in my head.

Currently atan2() says

This is equivalent to the arc tangent of y / x except
that the signs of y and x are used to determine the
quadrant of the result.

... not reeeeeally

It is not the equivalent of the arctangent. It IS the arctangent. If the
intended meaning was that it was the equivalent of atan() then it is
most definitely wrong because atan() returns a result in [-pi/2,+pi/2]
whereas atan2() returns a result in [-pi,+pi].

How about something like:

This is the arc (or inverse) tangent of y / x which lies in
the inclusive range [-pi,+pi] where the signs of y and x are
used to determine the quadrant of the result.

Somebody might have better words.

The words for atan() should qualify the range of its result.

Is this a bigger issue which means all ranges should be mentioned??

Sorry if I am making work for somebody.

1 Like

Hi Damian,

That all seems reasonable to me, thanks for pointing it out!

Lydia

PR is here if you'd like to take a look!

Lydia

1 Like

Just FYI, here are the words used in some help commands for atan2 (wikipedia). In the case of Julia (atan() overloaded for 1 and 2 args),

> ?atan
  atan(y)
  atan(y, x)

  Compute the inverse tangent of y or y/x, respectively.

  For one argument, this is the angle in radians between
  the positive x-axis and the point (1, y), returning a value
  in the interval [-\pi/2, \pi/2].

  For two arguments, this is the angle in radians between
  the positive x-axis and the point (x, y), returning a value
  in the interval [-\pi, \pi]. This corresponds to a standard atan2
  (https://en.wikipedia.org/wiki/Atan2) function. Note that
  by convention atan(0.0,x) is defined as \pi and atan(-0.0,x)
  is defined as -\pi when x < 0.

and Python (np.arctan2()):

>>> help(np.arctan2)
    arctan2(x1, x2, ...)

    Element-wise arc tangent of ``x1/x2`` choosing
    the quadrant correctly.
    
    The quadrant (i.e., branch) is chosen so that ``arctan2(x1, x2)``
    is the signed angle in radians between the ray ending
    at the origin and passing through the point (1,0), and the ray
    ending at the origin and passing through the point (`x2`, `x1`).
    (Note the role reversal: the "`y`-coordinate" is the first
    function parameter, the "`x`-coordinate" is the second.)
    By IEEE convention, this function is defined for `x2` = +/-0 and
    for either or both of `x1` and `x2` = +/-inf (see Notes for
    specific values).
    ...

BTW, I feel the de facto standard (?) order of the arguments
in many libraries ("y first, x second") is rather confusing (e.g. the above help page in Python)
and so wonder why the initial Fortran implementation chose such order historically...

PS: The above wikipage had some explanations (Argument order).

I was mainly worried about that use of equivalent and the fact that any wording needed to very forcefully remind people that atan() and atan2() return a result with a very different range.

For the latter reason, I was thinking the atan2() name should probably be preserved. But that puts me at odds with Stephen Wolfram and that's only a tiny step down from arguing with Donald Knuth. So, no, not going there. I will stick to arguing with Brad Chamberlain (and even then, I am probably wrong).

For clarity, I have no problem with atan2() as-is (i.e. the y -> x order), but was just curious about the history for this choice (and I am sorry to have derailed the topic somewhat...)
In practice, I guess it would be even more confusing to change the order at this stage, even if it were possible... (because almost all other languages and libraries use the y-> x order).

BTW, I found the phase() function for complex variables, which may be similar to atan2() for the range of the result (not tried yet).
https://chapel-lang.org/docs/modules/standard/Math.html#Math.phase

The phase() functions just calls atan2(). It is defined that way in the C Standard.

If you are bored, and you want to know why the reason for the argument ordering, read on.

In Fortran II (and I have the manuals), all the 20 intrinsic functions and the basic ones (which post 1959 or so were provided by the user group), i.e. LOG, SIN, COS, EXP, SQRT, ATAN, and TANH, called their arguments, Arg1 (and Arg2 where there was a second argument).

When it came to introducing the ATAN2() routine in Fortran between then and when it seems to have been published in 1961, that naming convention still held so it would have been written as

ATAN2(Arg1, Arg2)

and it would documented as returning

Arg1 / Arg2

Perfectly consistent. None of this x and y stuff to worry about alphabetical ordering.

Below are the definitions from the Reference Manual for the IBM 714 Data Processing Manual 1958 C28-6000-2. This was a minor revision of C28-6100-1 (which had a typo for SIGNF). This is the 1958 list (where I have added the (Arg1,Arg2) parameter list)

  MODF(Arg1,Arg2)           Arg1 - [Arg1/Arg2] where [ x ] = integral part of x
  MAX1F(Arg1,Arg2,..)       Max(Arg1, Arg2, ...)
  MIN1F(Arg1,Arg2,..)       Min(Arg1, Arg2, ...)
  SIGNF(Arg1,Arg2)          Sign of Arg2 times |Arg1|
  DIMF(Arg1,Arg2)           Arg1 - Min(Arg1, Arg2)

And ATAN2F(Arg1,Arg2) came along sometime in the next 3 years, appearing in the manual of 1961. Those routines all lost their trailing F in the early 1960s.

1 Like

Thanks very much for the detailed explanation! I see, so the original definition partly arises from the naming convention at that time ("Arg1", "Arg2", and so on) and also from the correspondence of atan2( arg1, arg2 ) to atan( arg1 / arg2 ) (so both 1 -> 2, with comma corresponding to slash). Given that atan2() was introduced as a variant of atan() (as its name suggests), I guess more emphasis was put on the formal similarity between the two functions, rather than as a function for returning the polar angle from 2D Cartesian coordinates (x,y).

(I guess my feeling about the order of atan2() is probably similar to the OP of the following question, because I also made a mistake when using atan2() much, much ago... To prevent such a mistake, I think I could define an alias like polar_angle(x, y) = atan2(y, x) etc, if really necessary.)

Also, thanks very much for the info about phase()! It is good to know that it uses atan2(),
so the result should be the same. I also searched the net a bit more, and the cmath module in Python also says their functions are also equivalent (apart from real versus complex arguments).

cmath.phase(x)

Return the phase of `x` (also known as the argument of `x`),
as a float. `phase(x)` is equivalent to `math.atan2(x.imag, x.real)`.
The result lies in the range [-π, π], and the branch cut for this
operation lies along the negative real axis.

It is amazing how many concepts trace their history to FortranII or Algol60 of the late 1950s. Way before my time!

With reference to that stackoverflow question which you quoted, those who answered it did not look back far enough to find the real reason for the ordering. Mind you, there is a big lack of available evidence (that I can find) of why things were done in Fortran during the early 1960s. No idea why this is the case. Some of it would be in the proceedings of the annual conferences of SHARE, the IBM user group, who wielded a lot of clout. The only copies of those which existed belonged to people who went to the conferences who are probably in their 80s or 90s now and have probably long ago binned them.

1 Like

Hi Takeshi,

Yeah, that's part of why we didn't make the change when stabilizing the
function - the strong precedence from other languages meant that
changing the ordering to be alphabetical would have been confusing (and
likely led to bugs for anyone using explicit argument naming)

Lydia

1 Like

Instead of your polar_angle function which accepts arguments of x and y which I will assume are real, you can choose to say

phase(x + y:imag)

which will return the value of atan2(y, x). If you want to use my cmplx procs, you could also say

phase(cmplx(x, y))

It is simply a case where the convention of trying to use a small list of names as a memory aid for the arguments (or parameters) breaks down. None of them are perfect. Too often they have limited domain to which they apply (like a lot of functions I guess!!)

Yes, when it comes to a question about the history, I have experienced a similar situation not only for StackOverflow but for other forums, i.e., people need to guess or speculate because of the lack of available references. (The notorious implicit (or implied) save feature in Fortran might be such a case, for which people think about various reasons why it was introduced back in ~1990, but available references seem very scarce.) So, I guess it may be nice to add an additional answer (explanation) to the above Stackoverflow page about atan2() if you possibly have an account on that site :wink:

Yeah, I completely agree. I think if one makes some parts of the design more convenient for a given purpose, other parts can become inconvenient for different purposes. The situation may be similar for various language features...

PS. I have some questions related to the phase() above, but will post it in separate threads or possibly on StackOverflow for Chapel (in case it is a more general question).

In the past, I have had problems posting to Stack Overflow.. It said I had to have some rating or score. Very weird. I never look at Stack Overflow as a matter of course. I find its format somewhat unreadable, not least because it uses different fonts.

Happy to answer any questions you have about phase. It is a very simple concept.

inline proc phase(z : complex(?w))
{
    return atan2(z.im, z.re);
}
1 Like