4

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

CitizenInsane
  • 4,755
  • 1
  • 25
  • 56
  • 1
    Is that code really supposed to read `value.Equals(value)`? Or rather something like `value.Equals(... some other value ...)`? – O. R. Mapper Aug 24 '12 at 12:26
  • var result = double.NaN.Equals(double.NaN); // result is true. – Stephan Schinkel Aug 24 '12 at 12:28
  • Why would you expect that testing a value is equal to itself to be false? – Peter Ritchie Aug 24 '12 at 12:28
  • 1
    @PeterRitchie I'm guessing that in cases of "not NaN" the expectation is that value *will* equal itself (and thus return "not NaN"). The other expectation is that NaN will not equal another NaN meaning the `isNaN` behaves as intended (returns NaN). The expectations are the problem. – Adam Houldsworth Aug 24 '12 at 12:30
  • @CitizenInsane Generics in this instance are more pain than they are worth, simply utilise `float.IsNaN` or `double.IsNaN` when required. – Adam Houldsworth Aug 24 '12 at 12:33
  • @Adam and all, I know this is more pain, and just testing for float/double is fine in practice. The effort is about having an elegant solution without knowledge about what type is. Even if again I do expect other types than float/double to define for some sort of 'NaN' value in practice. – CitizenInsane Aug 24 '12 at 21:49

2 Answers2

7

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).

Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
6

but Equals is returning true for double.NaN

Yes. And it does regardless of generics:

double x = double.NaN;
Console.WriteLine(x.Equals(x)); // True
Console.WriteLine(x == x); // False

Note that if the second line printed False, that would either make the IEquatable<T>.Equals inconsistent with the Equals(object) override, or you'd have to make Equals(object) violate the reflexivity requirement of object.Equals(object).

Basically this sort of thing is nasty whatever you do with it.

Given that you're trying to find max/min values, you might want to try using IComparable<T> instead of IEquatable<T> - that may well let you detect NaN in other ways. (I don't have time to check right now.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you for understanding what I truly meant by NaN.Equals(NaN) returns true. I already tried with IComparable but it was not conclusive. Anyway, your answer fully suit my needs and wonders, thanks :) – CitizenInsane Aug 24 '12 at 21:57