23
votes

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.

5
Floating point rounding error. - Keith
Pi cannot be represented exactly as a floating point number, so sin(pi) isn't going to be exactly zero. - Blender
Is there a fix or do I need to convert it to int? - MCF
You should check the values with np.allclose: np.allclose(np.sin(np.pi), 0) - Viktor Kerkez
@ViktorKerkez: You should post that as an answer. I'll write one in case you don't get to it, and delete it if you come back later. - abarnert

5 Answers

24
votes

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.

1
votes

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.

1
votes

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.

0
votes

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)
-1
votes

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