On Sat, Dec 28, 2019 at 11:29 PM Steve Barnes <gadgetst...@live.co.uk>
wrote:

>
>
> How about something along the lines of:
>
> def isnan(num):
>
>      """ Attempt at generic NaN check """
>
>      if hasattr(num, "is_nan"):  # Does it have its own method?
>
>          return num.is_nan()
>
>      else:
>
>          return num != num
>
> Then decimal would work nicely as would any potential types that are not
> self-equal would just need to define their own method albeit with a
> prescribed name. Of course it will return false for non-numeric types such
> as strings.
>

one trick here is sequences with a NaN in them:

In [56]: [1, 2, 3, float('NaN')] != [1, 2, 3, float('NaN')]

Out[56]: True

Which is why I'm leaning toward checking for a Number type:

def is_nan(num):
    """
    This version works as well
    """
    try:
        return num.is_nan()
    except AttributeError:
        if isinstance(num, Number):
            return num != num
        return False

This doesn't work with single-element numpy arrays (it does with numpy
scalars). Which is maybe fine -- at least for the statistics module, as
folks using numpy are probaly using a numpy statistics package anyway.

Though I'm not sure why it's better than:

def is_nan(num):
    """
    This version works for everything I've tried
    """
    try:
        return num.is_nan()
    except AttributeError:
        if isinstance(num, complex):
            return cmath.isnan(num)
        try:
            return math.isnan(num)
        except:
            return False

Which passes all the tests I came up with.

(code enclosed)

-CHB

-- 
Christopher Barker, PhD

Python Language Consulting
  - Teaching
  - Scientific Software Development
  - Desktop GUI and Web Development
  - wxPython, numpy, scipy, Cython
"""
some tests of an is_nan function
"""

import math
import cmath
from decimal import Decimal
from fractions import Fraction
from numbers import Number


import numpy as np
import pytest


def is_nan(num):
    """
    This version works for everything I've tried
    """
    try:
        return num.is_nan()
    except AttributeError:
        if isinstance(num, complex):
            return cmath.isnan(num)
        try:
            return math.isnan(num)
        except:
            return False

# def is_nan(num):
#     """
#     This version works as well
#     """
#     try:
#         return num.is_nan()
#     except AttributeError:
#         if isinstance(num, Number):
#             return num != num
#         return False


nan_vals = [Decimal('nan'),
            Decimal('snan'),
            float('nan'),
            np.nan,
            np.array(np.nan),  # zero dim array
            np.array([np.nan, ]),  # one element array
            np.array([np.nan]).reshape((1, 1, 1)),  # one element 3d array
            np.float32(np.nan),  # numpy scalar
            np.array(np.nan, dtype=np.float128),
            complex(float('nan'), 1),
            complex(0, float('nan')),
            complex(float('nan'), float('nan')),
            ]

non_nan_vals = [34,
                10**1000,  # an int too big for a float
                3.4,
                np.array(1.2e34),  # zero dim array
                np.array([1.2e34, ]),  # one element array
                np.array([1.2e34]).reshape((1, 1, 1)),  # one element 3d array
                np.float32(1.234),  # numpy scalar
                np.array(1.23, dtype=np.float32),
                np.array(1e200, dtype=np.float128) * 1e200,  # a numpy float128 too big for a float
                np.array([1.23, 3.45], dtype=np.float128)[0],
                Decimal('1e500'),
                Fraction(1, 10),
                Fraction(1, 1000000)**100,  # fraction too small for a float
                Fraction(10, 1) ** 500,  # fraction too large for a float
                complex(1, 2),
                complex(-123, 154),
                "a string",  # a string can never be a NaN
                [1, 2, 3],  # nor is a list
                [1, 2, 3, float('NaN')],  # even if it has a NaN in it.
                np.array([np.nan, np.nan]),  # more than one element array -- non NaN
                ]

exception_raising = []


@pytest.mark.parametrize("num", nan_vals)
def test_nan(num):
    assert is_nan(num)


@pytest.mark.parametrize("num", non_nan_vals)
def test_not_nan(num):
    assert not is_nan(num)


@pytest.mark.parametrize("num, exp", exception_raising)
def test_exception(num, exp):
    with pytest.raises(Exception):
        is_nan(num)


_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/PGDUY4ZRJX7VSKO62CRJDUOISA5PPA2S/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to