27

Does anyone know why the below doesn't equal 0?

import numpy as np
np.sin(np.radians(180))

or:

np.sin(np.pi)

When I enter it into python it gives me 1.22e-16.

MCF
  • 383
  • 1
  • 3
  • 6

6 Answers6

29

The number π cannot be represented exactly as a floating-point number. So, np.radians(180) doesn't give you π, it gives you 3.1415926535897931.

And sin(3.1415926535897931) is in fact something like 1.22e-16.

So, how do you deal with this?

You have to work out, or at least guess at, appropriate absolute and/or relative error bounds, and then instead of x == y, you write:

abs(y - x) < abs_bounds and abs(y-x) < rel_bounds * y

(This also means that you have to organize your computation so that the relative error is larger relative to y than to x. In your case, because y is the constant 0, that's trivial—just do it backward.)

Numpy provides a function that does this for you across a whole array, allclose:

np.allclose(x, y, rel_bounds, abs_bounds)

(This actually checks abs(y - x) < abs_ bounds + rel_bounds * y), but that's almost always sufficient, and you can easily reorganize your code when it's not.)

In your case:

np.allclose(0, np.sin(np.radians(180)), rel_bounds, abs_bounds)

So, how do you know what the right bounds are? There's no way to teach you enough error analysis in an SO answer. Propagation of uncertainty at Wikipedia gives a high-level overview. If you really have no clue, you can use the defaults, which are 1e-5 relative and 1e-8 absolute.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 3
    I actually have a good knowledge of error analysis so this is very helpful. – MCF Sep 06 '13 at 06:42
  • 2
    The error in this particular place will be almost exactly equal to the error in np.radians(180). That error is probably about 0.5 to 1.0 ULPs (units in the last place), or roughly 3.14 * DBL_EPSILON, or about 7e-16. My error estimate was a worst-case estimate so it is not surprising that the actual error is a bit smaller. See this article for the gory details (and some thoughts on how wonderfully cool this outcome really is): https://randomascii.wordpress.com/2014/10/09/intel-underestimates-error-bounds-by-1-3-quintillion/ – Bruce Dawson Nov 04 '15 at 22:14
3

One solution is to switch to sympy when calculating sin's and cos's, then to switch back to numpy using sp.N(...) function:

>>> # Numpy not exactly zero
>>> import numpy as np
>>> value = np.cos(np.pi/2)
6.123233995736766e-17

# Sympy workaround
>>> import sympy as sp
>>> def scos(x): return sp.N(sp.cos(x))
>>> def ssin(x): return sp.N(sp.sin(x))

>>> value = scos(sp.pi/2)
0

just remember to use sp.pi instead of sp.np when using scos and ssin functions.

pico
  • 1,660
  • 4
  • 22
  • 52
2

Faced same problem,

import numpy as np

print(np.cos(math.radians(90)))

>> 6.123233995736766e-17

and tried this,

print(np.around(np.cos(math.radians(90)), decimals=5))

>> 0

Worked in my case. I set decimal 5 not lose too many information. As you can think of round function get rid of after 5 digit values.

Meric Ozcan
  • 678
  • 5
  • 25
0

Try this... it zeros anything below a given tiny-ness value...

import numpy as np

def zero_tiny(x, threshold):
    if (x.dtype == complex):
        x_real = x.real
        x_imag = x.imag
        if (np.abs(x_real) < threshold): x_real = 0
        if (np.abs(x_imag) < threshold): x_imag = 0 
        return x_real + 1j*x_imag
    else:
        return  x if (np.abs(x) > threshold) else 0

value = np.cos(np.pi/2)
print(value)
value = zero_tiny(value, 10e-10)
print(value)

value = np.exp(-1j*np.pi/2)
print(value)
value = zero_tiny(value, 10e-10)
print(value)
pico
  • 1,660
  • 4
  • 22
  • 52
0

Python uses the normal taylor expansion theory it solve its trig functions and since this expansion theory has infinite terms, its results doesn't reach exact but it only approximates. For e.g sin(x) = x - x³/3! + x⁵/5! - ...

=> Sin(180) = 180 - ... Never 0 bout approaches 0.

That is my own reason by prove.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 08 '22 at 04:48
-2

Simple.

np.sin(np.pi).astype(int)
np.sin(np.pi/2).astype(int)
np.sin(3 * np.pi / 2).astype(int)
np.sin(2 * np.pi).astype(int)

returns

0
1
0
-1
Beomin
  • 1
  • 1
    You are forcing a type coercion, which would not help OP. While this makes `sin(pi) == 0`, this also makes `sin(pi/4) == 0`. – T Tse Feb 05 '20 at 20:06