35

While writing some test cases, and some of the tests check for the result of a NaN.

I tried using std::isnan but the assert failes:

Assertion `std::isnan(x)' failed.

After printing the value of x, it turned out it's negative NaN (-nan) which is totally acceptable in my case.

After trying to use the fact that NaN != NaN and using assert(x == x), the compiler does me a 'favor' and optimises the assert away.

Making my own isNaN function is being optimised away as well.

How can I check for both equality of NaN and -NaN?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
LiraNuna
  • 64,916
  • 15
  • 117
  • 140
  • 10
    o_O? Negative NaN *is* NaN. – kennytm Aug 29 '10 at 21:17
  • 1
    Could you show how you write your own `isNaN`, and perhaps the built-in one from your compiler if you have it? One way to test for **a** NaN (there are several, as you noticed) is to test the bit pattern at http://en.wikipedia.org/wiki/NaN (exponent is 11..11). – Pascal Cuoq Aug 29 '10 at 21:18
  • Which compiler are you using, and what does your test code look like? – jalf Aug 29 '10 at 21:20
  • 2
    @KennyTM From Wikipedia: "Since, in practice, encoded NaNs have both a sign and optional 'diagnostic information' (sometimes called a payload), these will often be found in string representations of NaNs, too, for example: -NaN". There is one NaN or plenty of NaNs depending how you want to look at it, the way this compiler prints it is probably more confusing than helpful. – Pascal Cuoq Aug 29 '10 at 21:20
  • @Pascal Cuoq: `bool isNaN(float x) { return x != x; }` – LiraNuna Aug 29 '10 at 21:22
  • 2
    This smells like a bug in your standard library's implementation of `std::isnan` to me. – Billy ONeal Aug 29 '10 at 21:23
  • There're more than two NaNs. It's not like inf. – Pavel Radzivilovsky Aug 29 '10 at 21:23
  • 1
    @jalf: `assert(std::isnan(x))`. Printing `x` before the assert shows `-nan`. – LiraNuna Aug 29 '10 at 21:24
  • @Billy ONeal: I really doubt glibc would have that kind of bug unnoticed. – LiraNuna Aug 29 '10 at 21:25
  • @LiraNuna I see now. If your compiler optimizes `x != x` to `false` when `x` has type `float`, I'm afraid you have to report this as a bug in your compiler. – Pascal Cuoq Aug 29 '10 at 21:26
  • @LiraNuna: Why? `isnan` should return true for all values of `nan`. Your example demonstrates behavior contrary to that. That's a bug. – Billy ONeal Aug 29 '10 at 21:29
  • 2
    Considering some of the horror stories I have already heard, I do not find the idea of such a bug even in glibc surprising. There is goodwill, don't get me wrong, but compiler authors have a long history of getting floating-point wrong. – Pascal Cuoq Aug 29 '10 at 21:33
  • @LiraNuna: As others have said, isnan is not standard C++ at this point. It is an extension taken from C99. For example, it is not portable to MSVC (even 2010). http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.15 – Merlyn Morgan-Graham Aug 29 '10 at 21:35

6 Answers6

48

This is embarrassing.

The reason the compiler (GCC in this case) was optimising away the comparison and isnan returned false was because someone in my team had turned on -ffast-math.

From the docs:

-ffast-math
    Sets -fno-math-errno, -funsafe-math-optimizations, -fno-trapping-math,
    -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans and
    fcx-limited-range.

    This option causes the preprocessor macro __FAST_MATH__ to be defined.

    This option should never be turned on by any -O option since it can result in
    incorrect output for programs which depend on an exact implementation of IEEE
    or ISO rules/specifications for math functions. 

Notice the ending sentence - -ffast-math is unsafe.

ZachB
  • 13,051
  • 4
  • 61
  • 89
LiraNuna
  • 64,916
  • 15
  • 117
  • 140
  • 2
    I just noticed the exact same problem with the MS compiler when used with /fp:fast flag. Works fine with /fp:precise though. – stijn Aug 27 '11 at 11:50
6

isnan() is expected to have undefined behaviour with -ffast-math.

This is what I use in my test suite:

#if defined __FAST_MATH__
#   undef isnan
#endif
#if !defined isnan
#   define isnan isnan
#   include <stdint.h>
static inline int isnan(float f)
{
    union { float f; uint32_t x; } u = { f };
    return (u.x << 1) > 0xff000000u;
}
#endif
sam hocevar
  • 11,853
  • 5
  • 49
  • 68
2

This looks like a bug in your library's implementation of isnan() to me. It works fine here on gcc 4.2.1 on Snow Leopard. However, what about trying this?

std::isnan(std::abs(yourNanVariable));

Obviously, I can't test it, since std::isnan(-NaN) is true on my system.

EDIT: With -ffast-math, irrespective of the -O switch, gcc 4.2.1 on Snow Leopard thinks that NAN == NAN is true, as is NAN == -NAN. This could potentially break code catastrophically. I'd advise leaving off -ffast-math or at least testing for identical results in builds using and not using it...

Chinmay Kanchi
  • 62,729
  • 22
  • 87
  • 114
1

There's C99 isnan() which you should be able to use.

If in your implementation it does not work correctly (which one is that?) you can implement your own, by reinterpret_casting to long and doing IEEE bit magic.

Pavel Radzivilovsky
  • 18,794
  • 5
  • 57
  • 67
0

You can check the bits of number. IEEE 754 has defined mask for NaN:

  • A signaling NaN is represented by any bit pattern between X'7F80 0001' and X'7FBF FFFF' or between X'FF80 0001' and X'FFBF FFFF'.
  • A quiet NaN is represented by any bit pattern between X'7FC0 0000' and X'7FFF FFFF' or between X'FFC0 0000' and X'FFFF FFFF'.

This might be not portable, but if you are sure about your platfofm it can be acceptable. More: http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=/com.ibm.xlf101l.doc/xlfopg/fpieee.htm

Andrey
  • 59,039
  • 12
  • 119
  • 163
  • That would've been my last resort, I'm still trying to find the most portable solution. – LiraNuna Aug 29 '10 at 21:31
  • @LiraNuna it is not that drammatic. If you implement it keeping endiannes in mind it will be portable enought – Andrey Aug 29 '10 at 21:53
-3

This is based off the wikipedia article posted in the comments. Note that it's entirely untested -- it should give you an idea of something you can do though.

bool reallyIsNan(float x)
{
    //Assumes sizeof(float) == sizeof(int)
    int intIzedX = *(reinterpret_cast<int *>(&x));
    int clearAllNonNanBits = intIzedX & 0x7F800000;
    return clearAllNonNanBits == 0x7F800000;
}

EDIT: I really think you should consider filing a bug with the GLibc guys on that one though.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • This will return `true` for `Inf`, too. – sam hocevar Sep 17 '11 at 14:28
  • @Sam: As I said, I did not test it, I got it from Wikipedia. I never made any claims about it's correctness. In any case, the OP already answered this question. I don't see why we're digging up a 1.5 year old question which was already marked answered and complaining about it. – Billy ONeal Sep 17 '11 at 17:24
  • 6
    I'm not complaining; you are. I found this question from a Google search and saw it had no satisfying answer. I'm commenting on incorrect or incomplete answers because I don't want someone else to spend time figuring out why they are incorrect or incomplete. Also, the approved answer gives an explanation, not a solution. How old this question is has absolutely no relevance. If you have a problem with that, you probably missed the point of this website. Just relax, dude. – sam hocevar Sep 18 '11 at 00:15
  • Oh by the way, I also think negative comments are an order of magnitude more civil than downvoting without a comment. – sam hocevar Sep 18 '11 at 00:36
  • The accepted answer does not detail anything and does not provide a workaround for the problem. And please, downvoted because I gave the same answer to the same question? Welcome to my personal list of negatively combative people I have no longer any intention of interacting with until they grow up somewhat. – sam hocevar Sep 19 '11 at 11:23