1

My code is quite simple, and only 1 line is causing an issue:

np.tan(np.radians(rotation))

Instead of my expected output for rotation = 45 as 1, I get 0.9999999999999999. I understand that 0 and a ton of 9's is 1. In my use case, however, it seems like the type of thing that will definitely build up over iterations.

What is causing the floating point error: np.tan or np.radians, and how do I get the problem function to come out correctly regardless of floating point inaccuracies?

Edit:

I should clarify that I am familiar with floating point inaccuracies. My concern is that as that number gets multiplied, added, and compared, the 1e-6 error suddenly becomes a tangible issue. I've normally been able to safely ignore floating point issues, but now I am far more concerned about the build up of error. I would like to reduce the possibility of such an error.

Edit 2:

My current solution is to just round to 8 decimal places because that's most likely enough. It's sort of a temporary solution because I'd much prefer a way to get around the IEEE decimal representations.

  • There is no error and I don't see exactly which issue you have to mitigate, since you should never test floating points values for equality. See also this R question about the subject (first answer has a language agnostic section that deserves to be learnt and understood): https://stackoverflow.com/questions/9508518/why-are-these-numbers-not-equal – nicola Jul 01 '19 at 15:43
  • 4
    Pi is transcendental. Pi/4 is transcendental. Neither is representable in a finite number of bits, consequently pi/4 is not a number representable in ieee754 floats or doubles. – EOF Jul 01 '19 at 15:45
  • @EOF I am aware the Pi is transcendental. What you said is true, but I want to make sure that after being operated on many times, the final number is accurate. –  Jul 01 '19 at 15:52
  • 1
    Do you _really_ get `0.9999999`? If so, that's a pretty big error (almost 1 billion ulps): . I get `0.9999999999999999` (actual stored value `0.99999999999999988897769753748434595763683319091796875`), which is only 1 ulp error - about the level of error that I'd expect. – Mark Dickinson Jul 01 '19 at 16:58
  • @MarkDickinson Sort of a noob question, but how can I get a full floating point value? I'm currently getting exactly 0.9999999999999999. –  Jul 01 '19 at 17:50
  • There are a couple of easy tricks. One is to use formatting with an explicit big-enough precision. For example, for a float `x` bigger than `0.5`, `format(x, ".53f")` will show you all the digits. Another is to convert to decimal: `decimal.Decimal(x)`. – Mark Dickinson Jul 01 '19 at 18:50

2 Answers2

1

What is causing the floating point error: np.tan or np.radians, and how do I get the problem function to come out correctly regardless of floating point inaccuracies?

Both functions incur rounding error, since in neither case is the exact result representable in floating point.

My current solution is to just round to 8 decimal places because that's most likely enough. It's sort of a temporary solution because I'd much prefer a way to get around the IEEE decimal representations.

The problem has nothing to do with decimal representation, and this will give worse results outside of the exact case you mention above, e.g.

>>> np.tan(np.radians(60))
1.7320508075688767
>>> round(np.tan(np.radians(60)), 8)
1.73205081
>>> np.sqrt(3) # sqrt is correctly rounded, so this is the closest float to the true result
1.7320508075688772

If you absolutely need higher accuracy than the 15 decimal digits you would get from code above, then you can use an arbitrary precision library like gmpy2.

Simon Byrne
  • 7,694
  • 1
  • 26
  • 50
0

Take a look here: https://docs.scipy.org/doc/numpy/user/basics.types.html .

Standard dtypes in numpy do not go beyond 64 bits precision. From the docs:

Be warned that even if np.longdouble offers more precision than python float, it is easy to lose that extra precision, since python often forces values to pass through float. For example, the % formatting operator requires its arguments to be converted to standard python types, and it is therefore impossible to preserve extended precision even if many decimal places are requested. It can be useful to test your code with the value 1 + np.finfo(np.longdouble).eps.

You can increase precision with np.longdouble, but this is platform dependent

In spyder (windows):

np.finfo(np.longdouble).eps  #same precision as float
>> 2.220446049250313e-16
np.finfo(np.longdouble).precision
>> 15

In google colab:

np.finfo(np.longdouble).eps  #larger precision
>> 1.084202172485504434e-19
np.finfo(np.longdouble).precision
>> 18

print(np.tan(np.radians(45, dtype=np.float), dtype=np.float) - 1)
print(np.tan(np.radians(45, dtype=np.longfloat), dtype=np.longfloat) - 1)
>> -1.1102230246251565e-16
0.0
Tarifazo
  • 4,118
  • 1
  • 9
  • 22