Search code examples
pythonlimit

How to check if function's input is within the data type limit?


I have a function which takes an array-like argument an a value argument as inputs. During the unit tests of this function (I use hypothesis), if a very large value is thrown (one that cannot be handled by np.float128), the function fails.

What is a good way to detect such values and handle them properly?

Below is the code for my function:

def find_nearest(my_array, value):
    """ Find the nearest value in an unsorted array.
    """
    # Convert to numpy array and drop NaN values.
    my_array = np.array(my_array, copy=False, dtype=np.float128)
    my_array = my_array[~np.isnan(my_array)]

    return my_array[(np.abs(my_array - value)).argmin()]

Example which throws an error:

find_nearest([0.0, 1.0], 1.8446744073709556e+19)

Throws: 0.0, but the correct answer is 1.0.

If I cannot throw the correct answer, at least I would like to be able to throw an exception. The problem is that now I do not know how to identify bad inputs. A more general answer that would fit other cases is preferable, as I see this as a recurring issue.


Solution

  • Beware, float128 isn't actually 128 bit precision! It's in fact a longdouble implementation: https://en.wikipedia.org/wiki/Extended_precision. The precision of this type of storage is 63 bits - this is why it fails around 1e+19, because that's 63 binary bits for you. Of course, if the differences in your array is more than 1, it will be able to distinguish that on that number, it simply means that whatever difference you're trying to make it distinguish must be larger than 1/2**63 of your input value.

    What is the internal precision of numpy.float128? Here's an old answer that elaborate the same thing. I've done my test and have confirmed that np.float128 is exactly a longdouble with 63 bits of precision.

    I suggest you set a maximum for value, and if your value is larger than that, either:

    1. reduce the value to that number, on the premise that everything in your array is going to be smaller than that number.

    2. Throw an error.

    like this:

    VALUE_MAX = 1e18
    def find_nearest(my_array, value):
        if value > VALUE_MAX:
            value = VALUE_MAX
        ...
    

    Alternatively, you can choose more scientific approach such as actually comparing your value to the maximum of the array:

    def find_nearest(my_array, value):
        my_array = np.array(my_array, dtype=np.float128)
        if value > np.amax(my_array):
            value = np.amax(my_array)
        elif value < np.amin(my_array):
            value = np.amin(my_array)
        ...
    

    This way you'll be sure that you never run into this problem - since your value will always be at most as large as the maximum of your array or at minimum as minimum of your array.