'The numpy angle function returns different answers for the same input?

I am using Python 3.7.7 and numpy 1.19.1. This is the code:

import numpy as np
a = 55.74947517067784019673 + 0j
print(f'{-a == -1 * a}, {np.angle(-a)}, {np.angle(-1 * a)}')

and this is the output:

True, -3.141592653589793, 3.141592653589793

I have two questions:

  1. Why does the angle function give different outputs for the same input?
  2. According to the documentation, the angle output range is (-pi, pi], so why is one of the outputs -np.pi?


Solution 1:[1]

If you look at the source of the np.angle, it uses the function np.arctan2. Now, according to the numpy docs, np.arctan2 uses the underlying C library, which has the following rule:

Note that +0 and -0 are distinct floating point numbers, as are +inf and -inf.

which results in different behavior when calculating using +/-0. So, in this case, the rule is:

y: +/- 0
x: <0
angle: +/- pi

Now, if you try:

a = 55.74947517067784019673
print(f'{-a == -1 * a}, {np.angle(-a)}, {np.angle(-1 * a)}')
#True, 3.141592653589793, 3.141592653589793

and if you try:

a = 55.74947517067784019673 + 0j
print(-a)
#(-55.74947517067784-0j)
print(-1*a)
#(-55.74947517067784+0j)
print(f'{-a == -1 * a}, {np.angle(-a)}, {np.angle(-1 * a)}')
#True, -3.141592653589793, 3.141592653589793

Which is inline with the library protocol.

As for your second question, I guess it is a typo/mistake since the np.arctan2 doc says:

Array of angles in radians, in the range [-pi, pi]. This is a scalar if both x1 and x2 are scalars.

Explanation of -a vs. -1*a:

To start with, 55.74947517067784019673 + 0j is NOT construction of a complex number and merely addition of a float to a complex number (to construct a complex number explicitly use complex(55.74947517067784019673, 0.0) and beware that integers do not have signed zeros and only floats have). -a is simply reverting the sign and quite self explanatory. Lets see what happens when we calculate -1*a:

For simplicity assume a = 55.5 + 0j

  • First a = 55.5+0j converts to complex(55.5, 0.0)
  • Second -1 equals to complex(-1.0, 0.0)
  • Then complex(-1.0, 0.0)*complex(55.5, 0.0) equals to complex((-1.0*55.5 - 0.0*0.0), (-1.0*0.0 + 0.0*55.5)) equals to complex((-55.5 - 0.0), (-0.0 + 0.0)) which then equals to complex(-55.5, 0.0).

Note that -0.0+0.0 equals to 0.0 and the sign rule only applies to multiplication and division as mentioned in this link and quoted in comments below. To better understand it, see this:

print(complex(-1.0, -0.0)*complex(55.5, 0.0))
#(-55.5-0j)

where the imaginary part is (-0.0*55.5 - 1.0*0.0) = (-0.0 - 0.0) = -0.0

Solution 2:[2]

For 1) print -a and -1*a, you'll see they are different.

-a
Out[4]: (-55.74947517067784-0j)

-1*a
Out[5]: (-55.74947517067784+0j) # note +0j not -0j

Without knowing the details of the numpy implementation, the sign of the imaginary part is probably used to compute the angle... which could explain why this degenerate case gives different results.

For 2) this looks like a bug or a doco mistake to me then...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2