Search code examples
pythonnumpyangle

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

  • 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