3

I was wondering what the differences between the following types of nan were. Except for the visual difference of NAN_macro (which evaluates to -nan(ind) as opposed to just nan), they all seem to behave the same (as per the sample script below).

I had a look at some other answers, e.g. What is difference between quiet NaN and signaling NaN?. But I still don't quite understand why NAN is -nan(ind) whereas std::numeric_limits<double>::quiet_NaN() is nan, or why we have std::nan("") and std::nan("1") if at the end of the day they both seem to be the same concept.

Any explanations or clarifying links would be great.

#include <cmath>
#include <limits>

int main()
{
    using num_lim = std::numeric_limits<double>;

    const double NAN_macro   = static_cast<double>(NAN);  // -nan(ind)
    const double NAN_quiet   = num_lim::quiet_NaN();      // nan
    const double NAN_sig     = num_lim::signaling_NaN();  // nan
    const double NAN_str     = std::nan("");              // nan
    const double NAN_str1    = std::nan("1");             // nan
    const double NAN_strLong = std::nan("some string");   // nan

    const bool isnan_macro   = std::isnan(NAN_macro);    // true
    const bool isnan_quiet   = std::isnan(NAN_quiet);    // true
    const bool isnan_sig     = std::isnan(NAN_sig);      // true
    const bool isnan_str     = std::isnan(NAN_str);      // true
    const bool isnan_str1    = std::isnan(NAN_str1);     // true
    const bool isnan_strLong = std::isnan(NAN_strLong);  // true

    const bool not_equal_macro   = (NAN_macro   != NAN_macro);   // true
    const bool not_equal_quiet   = (NAN_quiet   != NAN_quiet);   // true
    const bool not_equal_sig     = (NAN_sig     != NAN_sig);     // true
    const bool not_equal_str     = (NAN_str     != NAN_str);     // true
    const bool not_equal_str1    = (NAN_str1    != NAN_str1);    // true
    const bool not_equal_strLong = (NAN_strLong != NAN_strLong); // true

    const double sum_macro   = 123.456 + NAN_macro;    // -nan(ind)
    const double sum_quiet   = 123.456 + NAN_quiet;    // nan
    const double sum_sig     = 123.456 + NAN_sig;      // nan
    const double sum_str     = 123.456 + NAN_str;      // nan
    const double sum_str1    = 123.456 + NAN_str1;     // nan
    const double sum_strLong = 123.456 + NAN_strLong;  // nan
}
Phil-ZXX
  • 2,359
  • 2
  • 28
  • 40

1 Answers1

2

IEEE Standard 754 represents a NaN by a bit pattern with an exponent of all 1s and a non-zero fraction (note that all floating point decimal values are represented by a "sign", an "exponent" and a"fraction"), then, there are a lot of different NaN you can represent, as "a non-zero fraction" may be lots of different values.

To highlight your NaN representations, extend your code with this:

#include <cmath>
#include <limits>
#include <bitset>
#include <iostream>

union udouble {
  double d;
  unsigned long long u;
};

void Display(double doubleValue, char* what)
{
    udouble ud;
    ud.d = doubleValue;
    std::bitset<sizeof(double) * 8> b(ud.u);
    std::cout << "BitSet : " << b.to_string() << " for " << what << std::endl;
}

int main()
{
    using num_lim = std::numeric_limits<double>;

    const double NAN_macro   = static_cast<double>(NAN);  // -nan(ind)
    const double NAN_quiet   = num_lim::quiet_NaN();      // nan
    const double NAN_sig     = num_lim::signaling_NaN();  // nan
    const double NAN_str     = std::nan("");              // nan
    const double NAN_str1    = std::nan("1");             // nan
    const double NAN_strLong = std::nan("some string");   // nan

    ...

    Display( NAN_macro, "NAN_macro" );
    Display( NAN_quiet, "NAN_quiet" );
    Display( NAN_sig, "NAN_sig" );
    Display( NAN_str, "NAN_str" );
    Display( NAN_str1, "NAN_str1" );
    Display( NAN_strLong, "NAN_strLong" );
}

Even if it has has some UB (that could be fixed, see Ruslan comment), it does work to illustrate what I'm explaining below), this program outputs:

BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_macro
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_quiet
BitSet : 0111111111110100000000000000000000000000000000000000000000000000 for NAN_sig
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_str
BitSet : 0111111111111000000000000000000000000000000000000000000000000001 for NAN_str1
BitSet : 0111111111111000000000000000000000000000000000000000000000000000 for NAN_strLong

They all have:

  • "0" as sign (you'll probably get "1" for NAN_macro, as you report it as -nan, I don't). I used g++, I bet NAN macro may be defined differently by some compilers, I doubt this is part of C++ standard anyway.
  • "11111111111" as exponent
  • then different values of "fraction"...any actually, but never only zeros, else it would not be a NaN anymore.

Actually, this "fraction" or "payload" (see Ruslan comment) value can be used to store any information you'll like to store (as "why was there a nan here"?), this is called, NaN-boxing.

That's mainly why you may have "different" NaN values at some point (quiet_NaN, signaling_NaN or any NaN you create conform to IEEE Standard 754...even if having different memory representations, thay are all NaN values (x!=x and std::isnan(x)==true).

So, to answer your questions:

why NAN is -nan(ind) whereas std::numeric_limits<double>::quiet_NaN() is nan

Likelly because your compiler defined NAN macro like that, it may be different using another compiler. By the way, it's like min/max macros, even if one had the bad idea to define them, don't use them, prefer std functions which are part of the standard so should work the same with any compiler you use.

why we have std::nan("") and std::nan("1") if at the end of the day they both seem to be the same concept.

Maybe "to help you play with NaN-boxing" could be an answer, even if I doubt those functions were created for that specific purpose. The right answer may just be "to let you decide what "fraction" value you want to use for your NaN if you need something different than std::quiet_NaN and std::signaling_NaN"

Sources: https://steve.hollasch.net/cgindex/coding/ieeefloat.html

Also used https://stackoverflow.com/a/40737266/3336423 to output NaN memory representation.

jpo38
  • 20,821
  • 10
  • 70
  • 151
  • You can avoid the UB due to union-type-punning by employing `memcpy`, see [How to implement fast inverse sqrt without undefined behavior?](https://stackoverflow.com/q/24405129/673852). – Ruslan Apr 22 '20 at 17:50
  • 1
    What you call NaN "fraction" is properly called _payload_, and its purpose is to enable tracking the origin of the NaN. See [Why does IEEE 754 reserve so many NaN values?](https://stackoverflow.com/q/19800415/673852). – Ruslan Apr 22 '20 at 17:53
  • FYI. My compiler is MSVC 2019, and `NAN_macro` is `1111111111111000000000000000000000000000000000000000000000000000`, i.e. `1` in the first bit. I guess that's why it displays as `-nan(ind)`. – Phil-ZXX Apr 25 '20 at 18:07
  • @Phil-ZXX: I'm also seeing this with MSVC2015. I recommend that you never use MSVC macros anyway. Thanks for the bounty! – jpo38 Apr 25 '20 at 18:44