Checking for NaN

The Partridge Family were neither partridges nor a family. Discuss.
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Checking for NaN

Post by albinopapa » August 27th, 2020, 9:12 pm

If you look up how to check a floating point value for NaN, the usual response is to test that number against itself. The reason for this is because the IEEE754 standard says that a value of NaN is not comparable to any other value including itself.

So normally, you'd have
bool is_nan( float x ){ return x!=x; }

You could also use the std::isnan() function from <cmath>.

Recently, I wanted to do this in a constexpr context and VS doesn't have a constexpr version of isnan(), and what's weird is testing for NaN in a constexpr context give an error of ( floating-point values cannot be compared ).

Enter C++20 and std::bit_cast.

Code: Select all

#include <bit>
#include <cstdint>
#include <limits>
#include <type_traits>

template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
constexpr bool is_nan( T x )noexcept { 
	using integer_type = std::conditional_t<std::is_same_v<T, float>, std::uint32_t, std::uint64_t>;

	constexpr auto qnan = std::bit_cast<integer_type, T>( std::numeric_limits<T>::quiet_NaN() );
	constexpr auto snan = std::bit_cast<integer_type, T>( std::numeric_limits<T>::signaling_NaN() );

	const auto bits = std::bit_cast<integer_type, T>( x );

	return bits == qnan || bits == snan;
}
Now, we can write a constexpr is_nan() function.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: Checking for NaN

Post by Slidy » August 28th, 2020, 5:16 am

I don't think this works properly.

Test case:

Code: Select all

constexpr bool my_is_nan( T x )noexcept {
	using integer_type = std::conditional_t<std::is_same_v<T, float>, std::uint32_t, std::uint64_t>;

	constexpr auto qnan = std::bit_cast<integer_type, T>(std::numeric_limits<float>::quiet_NaN());
	constexpr auto snan = std::bit_cast<integer_type, T>(std::numeric_limits<float>::signaling_NaN());

	const auto bits = std::bit_cast<integer_type, T>(x);

	return bits == qnan || bits == snan;
}

int main()
{
	union
	{
		double f;
		struct
		{
			uint64_t mantissa : 52;
			uint64_t exponent : 11;
			uint64_t sign : 1;
		};
	} x;

	x.f = nan("");
	std::cout << "IsNaN: " << isnan( x.f ) << " MyIsNaN: " << my_is_nan( x.f ) << " (sign=" << x.sign << ", exponent=" << x.exponent << ", mantissa=" << x.mantissa << ")" << std::endl;

	x.mantissa |= 0xFFFF;
	std::cout << "IsNaN: " << isnan( x.f ) << " MyIsNaN: " << my_is_nan( x.f ) << " (sign=" << x.sign << ", exponent=" << x.exponent << ", mantissa=" << x.mantissa << ")" << std::endl;
}
Output:

Code: Select all

IsNaN: 1 MyIsNaN: 1
IsNaN: 1 MyIsNaN: 0
A floating point number has a format that looks something like this:
Image

A NaN is (usually) a number where all the exponent bits are set to 1. Whether it's a quiet or signaling NaN is determined by the highest bit of the mantissa. In a double, that leaves 51 bits (+ sign bit) that could be set to anything while still being considered NaN.
Image

This is also mentioned in the cppreference page for queit_NaN and signaling_NaN: "In IEEE 754, the most common binary representation of floating-point numbers, any value with all bits of the exponent set and at least one bit of the fraction set represents a NaN. It is implementation-defined which values of the fraction represent quiet or signaling NaNs, and whether the sign bit is meaningful."

In fact, this property is used by dynamic languages to pack multiple types into a single double by using those free NaN bits to store data. This is known as NaN boxing, you can read about it here (got images from here too): https://craftinginterpreters.com/optimization.html

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Checking for NaN

Post by albinopapa » August 28th, 2020, 8:54 am

Well, damn, forgot to check doubles. There was an error in the code, try again and see if that works for you.

The original code:

Code: Select all

	constexpr auto qnan = std::bit_cast<integer_type, T>( std::numeric_limits<float>::quiet_NaN() );
	constexpr auto snan = std::bit_cast<integer_type, T>( std::numeric_limits<float>::signaling_NaN() );
The corrected code:

Code: Select all

	constexpr auto qnan = std::bit_cast<integer_type, T>( std::numeric_limits<T>::quiet_NaN() );
	constexpr auto snan = std::bit_cast<integer_type, T>( std::numeric_limits<T>::signaling_NaN() );
When I originally made this, I was only testing floats then turned it into a template function, so I missed a template parameter for std::numeric_limits.

Seems to be working for me now with doubles as well.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Checking for NaN

Post by albinopapa » August 28th, 2020, 8:56 am

As far as bit pattern of floating point values, yeah, I spent a few minutes on cppreference.com as well. This should work since the main portion of the bit pattern is defined while the sign bit and mantissa bit for signaling and quiet nan is implementation defined and I'm getting the qnan and snan from the library and not picking one at random.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: Checking for NaN

Post by Slidy » August 28th, 2020, 12:23 pm

This doesn't work either. As I mentioned, since the mantissa bits can be anything while still being considered NaN you can't just do a direct compare. One workaround might be to mask out the mantissa/sign bits before doing a compare.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Checking for NaN

Post by albinopapa » August 28th, 2020, 6:07 pm

It's funny, I'm testing in a constexpr context using std::numeric_limits<float>::quiet_NaN() and std::numeric_limits<float>::signaling_NaN() and get the correct results. I haven't tested in a non-constexpr context.

Okay, tested using your main() code and my is_nan() function.

Code: Select all

IsNaN: 1 MyIsNaN: 1 (sign=0, exponent=2047, mantissa=2251799813685248)
IsNaN: 1 MyIsNaN: 1 (sign=0, exponent=2047, mantissa=2251799813685248)
This uses double and that weird C nan() function.

Since it's from the same implementation ( microsoft surely has a specific set of bits to check ) a quiet nan or signaling nan should have the same bit pattern. Now comparing Microsoft's implementation to GCC or Clang I could understand not having the same bit patterns.

It's weird that it I get the correct and expected results on my computer.

Code: Select all

#include <bit>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <type_traits>

template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
constexpr bool is_nan( T x )noexcept {
	using integer_type = std::conditional_t<std::is_same_v<T, float>, std::uint32_t, std::uint64_t>;

	constexpr auto qnan = std::bit_cast< integer_type, T >( std::numeric_limits<T>::quiet_NaN() );
	constexpr auto snan = std::bit_cast< integer_type, T >( std::numeric_limits<T>::signaling_NaN() );

	const auto bits = std::bit_cast< integer_type, T >( x );

	return bits == qnan || bits == snan;
}

int main( int argc, char* argv[] ) {
	union
	{
		double f;
		struct
		{
			uint64_t mantissa : 52;
			uint64_t exponent : 11;
			uint64_t sign : 1;
		};
	} x;

	x.f = std::nan( "" );
	std::cout << "IsNaN: " << std::isnan( x.f ) << " MyIsNaN: " << is_nan( x.f ) << " (sign=" << x.sign << ", exponent=" << x.exponent << ", mantissa=" << x.mantissa << ")" << std::endl;
	std::cout << "IsNaN: " << std::isnan( x.f ) << " MyIsNaN: " << is_nan( x.f ) << " (sign=" << x.sign << ", exponent=" << x.exponent << ", mantissa=" << x.mantissa << ")" << std::endl;

	return 0;
}

If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Checking for NaN

Post by albinopapa » August 28th, 2020, 6:16 pm

Code: Select all

IsNaN: true MyIsNaN: true (sign=0, exponent=7ff, mantissa=8000000000000)
IsNaN: true MyIsNaN: true (sign=0, exponent=7ff, mantissa=8000000000000)
Changed output formatting to get bool string ( "true", "false" ) and hex values.
On my computer, the only difference between SNAN and QNAN is:
QNAN mantissa: 8000000000000
SNAN mantissa: 8000000000001
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: Checking for NaN

Post by Slidy » August 28th, 2020, 6:45 pm

albinopapa wrote:
August 28th, 2020, 6:07 pm
Okay, tested using your main() code and my is_nan() function.

Code: Select all

IsNaN: 1 MyIsNaN: 1 (sign=0, exponent=2047, mantissa=2251799813685248)
IsNaN: 1 MyIsNaN: 1 (sign=0, exponent=2047, mantissa=2251799813685248)
Don't think that was my exact code you tested, you're testing the same mantissa twice. In my test I changed the mantissa for the second check:

Code: Select all

x.mantissa |= 0xFFFF;
And yes, only difference between SNAN/QNAN is one of the mantissa bits (check image in my first post in this thread).

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Checking for NaN

Post by albinopapa » August 28th, 2020, 7:00 pm

Okay, didn't notice what you did in your original code. I am specifically testing against the qnan and snan bit patterns defined by the library, and you are trying to use any value for mantissa.

I don't like it, but you may have a point. I don't ever see a case where the nans will ever be a bunch of random bits in the mantissa, it is possible.

My point is I get the nans to compare to from the implementation, which the bit pattern for the nans is implementation defined, so I feel it is safe enough.

Aside from std::nan() and the std::numeric_limits functions, how would you even getting a nan as a result? I tried dividing by 0.0 and end up getting inf. I tried using an uninitialized variable and it wouldn't compile. The only time I've seen nan come up is when doing some SIMD coding. Since a true mask is all bits set, you get ( nan, nan, nan, nan ) for a true mask float32 SSE value.

In this case, yes, my test would fail. I'd have to mask out the sign bit and the mantissa since they don't matter. Thanks for the patience and pointing it out.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Checking for NaN

Post by albinopapa » August 28th, 2020, 7:10 pm

Code: Select all

template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
constexpr bool is_nan( T x )noexcept {
	using integer_type = std::conditional_t<std::is_same_v<T, float>, std::uint32_t, std::uint64_t>;
	constexpr auto nan = ( std::is_same_v<T, float> ? 0x7FC00000u : 0x7FF8000000000000u );
	const auto bits = std::bit_cast< integer_type, T >( x ) & nan;
	return bits == nan;
}

int main( int argc, char* argv[] ) {
	union pun
	{
		constexpr pun( double v_ ) :f( v_ ) {}
		constexpr pun operator|( std::uint64_t val )const noexcept {
			auto result = *this;
			result.mantissa |= val;
			return result;
		}
		double f;
		struct
		{
			uint64_t mantissa : 52;
			uint64_t exponent : 11;
			uint64_t sign : 1;
		};
	};

	constexpr pun x = std::numeric_limits<decltype(pun::f)>::signaling_NaN();
	auto print_result = []( pun x ) {
		std::cout
			<< "std::isnan: "	 << std::boolalpha << std::isnan( x.f )
			<< " is_nan: "  << is_nan( x.f )
			<< " ( sign="	 << std::dec << x.sign
			<< ", exponent=" << std::hex << x.exponent
			<< ", mantissa=" << x.mantissa << " )\n";
	};

	print_result( x );
	print_result( x | 0xFFFFull );

	return 0;
}

If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply