Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test for NaN with generics (or why NaN.Equals(NaN) == true)?

Tags:

c#

iequatable

I need to find min and max values in an array (not taking into for possible NaN values in this array).

This would be easy only working with double, but these FindMin and FindMax functions have to work with generics types.

I have tried to test for generic NaNs in this way:

bool isNaN<T>(T value) where T : IEquatable<T>
{
    return !value.Equals(value);
}

but Equals is returning true for double.NaN ??!!

I have a workaround like this for now:

bool isNaN<T>(T value) where T : IEquatable<T>
{
    var d = value as double?;
    if (d.HasValue) { return double.IsNaN(d.Value); }

    return !value.Equals(value);
}

My question is more about understanding why first solution did not work, is this a bug ?

You can find small test code here

like image 486
CitizenInsane Avatar asked Dec 12 '22 23:12

CitizenInsane


1 Answers

I would simply use double.IsNaN and let the compiler implicitly cast float members where required:

float myFloat = float.NaN; // or 0.0f / 0.0f;
double myDouble = double.NaN; // or 0.0 / 0.0;

Console.WriteLine(double.IsNaN(myFloat));
Console.WriteLine(double.IsNaN(myDouble));

"Wastes" a very small amount of memory on the stack and uses a cast op call, but hey ho it is generic enough to cater for any type that can hold a "numeric" NaN.

Alternatively, using float.IsNaN appears to work with rudimentary testing, but requires an explicit downcast of a double (downcasting double.NaN and 0.0 / 0.0 appears to work, as in, it reports NaN correctly).

You cannot generically constrain on a subset of value types with any cleanliness (or at all, I'm not 100% certain) so I wouldn't bother chasing the <T> route personally.


If you want a general solution that means the callee doesn't need to care what is passed in, you can do the following:
    static bool IsNaN(dynamic d)
    {
        float dub;

        try
        {
            dub = (float)d;
            return float.IsNaN(dub);
        }
        catch (RuntimeBinderException)
        {
        }

        return false;
    }

However, this incurs boxing. Note as well that dynamic is required and object will not work, so this also invokes the DLR (and also swallows all RuntimeBinderExceptions).

like image 153
Adam Houldsworth Avatar answered Mar 03 '23 18:03

Adam Houldsworth