Home > other >  Comparison between numpy array and float number of the same data type?
Comparison between numpy array and float number of the same data type?

Time:01-25

np.arange(0, 1, 0.1) initializes an float point array with the defualt data type float64. However, when I use <= to compare it to, say, np.float64(0.6), the 7th element (0.6) returns False. What's even more weird is that if I use float32 for initialization and comparison, the result becomes just right. What's the explanation for this?

CodePudding user response:

The answer is pretty obvious if you do this:

import numpy as np

a = np.arange(0, 1, 0.1)

print('\n'.join(map(str, zip(a, a >= np.float64(0.6)))))

Result:

(0.0, False)
(0.1, False)
(0.2, False)
(0.30000000000000004, False)
(0.4, False)
(0.5, False)
(0.6000000000000001, True)
(0.7000000000000001, True)
(0.8, True)
(0.9, True)

It's just a classic case of this: Is floating point math broken?

You asked why this isn't a problem for float32. For example:

import numpy as np

a = np.arange(0, 1, 0.1, dtype=np.float32)

print('\n'.join(map(str, zip(a, a < np.float32(0.6)))))

Result:

(0.0, True)
(0.1, True)
(0.2, True)
(0.3, True)
(0.4, True)
(0.5, True)
(0.6, False)
(0.7, False)
(0.8, False)
(0.90000004, False)

The clue is in the length of that last value. Notice how 0.90000004 is a lot shorter than 0.30000000000000004 and 0.6000000000000001. This is because there is less precision available in 32 bits than there is in 64 bits.

In fact, this is the entire reason to use 64-bit floats over 32-bit ones, when you need the precision. Depending on your system's architecture, 64-bit is likely to be a bit slower and certain to take up twice the space, but the precision is better. How exactly depends on the implementation of the floating point number (there are many choices that are too technical and detailed to go into here) - but there's twice the number of bits available to store information about the number, so you can see how that allows an increase in precision.

It just so happens that in 32 bits, the format has a representation of 0.6 that has enough zeroes for it to just say 0.6 (instead of 0.60000000). In 64-bits, the best values to represent 0.6 have even more zeroes, but a non-zero gets in at the end, showing the inaccuracy of the representation in that format.

It seems counterintuitive that float32 is 'more precise' than float64 in this case, but that's just a matter of cherry-picking. If you looked at a large random selection of numbers, you'd find that float64 gets a lot closer on average. It just so happens that float32 appears more accurate by accident.

The key takeaway here is that floating point numbers are an approximation of the real numbers. They are sufficiently accurate for most everyday operations and the errors tend to average out over time for many use cases, if the format is well-designed. However, because there is some error involved in most cases (of course some numbers just happen to get accurately represented, every point in the floating point type still falls on the real number line), when printing floating point numbers, some rounding is generally required as a result.

My favourite example to show that imprecision shows up early in Python (or any language with floats really):

>>> .1   .1   .1 == .3
False
>>> print(.1   .1   .1, f'{.1   .1   .1:.1f}')
0.30000000000000004 0.3

And if you need better precision, you can look at types like decimal. Also, in very specific cases more bits than 64 may be available, but that's likely to lead to surprises around support and I would not recommend it.

  •  Tags:  
  • Related