NAN Differences - std::nan vs quiet_NaN() vs Macro NAN

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
}

1 Answer

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:

Also used to output NaN memory representation.

4

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like