Currently I am able to use the compiler intrinsic __builtin_nan("") to repersent a NaN value and this can be used with gcc 6.1+, clang 6.0.0+, and MSVC 19.24+, but I would like to support even older compilers if possible. I also don't think it would be proper to use std::nan over the compiler intrinsic as it is not required to be constexpr by the standard and it would prevent the function from being used in static_asserts. Is there a manner to represent NaN in a constexpr context for compilers that may not have this intrinsic?
#How to represent a constexpr NaN with older C++ compilers
50 messages · Page 1 of 1 (latest)
When your question is answered use !solved to mark the question as resolved.
Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.
real question is, why are you using NaN for in constexpr context
/*
* Copyright (c) 2024-Present Ian Pike
* Copyright (c) 2024-Present ccmath contributors
*
* This library is provided under the MIT License.
* See LICENSE for more information.
*/
#pragma once
#include <cstdint>
// __builtin_nan only works with gcc 6.1+, clang 6.0.0+, or MSVC 19.24+
// TODO: Add support for other compilers that support __builtin_nan and are constexpr
#ifndef BUILTIN_NAN_SUPPORTED
#if ((defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 601)) || (defined(__clang__) && ((__clang_major__ * 100 + __clang_minor__) >= 600)) || \
(defined(_MSC_VER) && (_MSC_VER >= 1924))) || \
(defined(__has_builtin) && __has_builtin(__builtin_nan))
#define BUILTIN_NAN_SUPPORTED
#else
// Sadly we have to include all of cmath to get std::nan, but since ccm::nan is rarely used it should be fine
#include <cmath> // for std::nan (if __builtin_nan is not supported)
#endif
#endif
namespace ccm
{
template <typename T>
inline constexpr T nan(T num)
{
// __builtin_nan is constexpr in gcc 6.1+, clang 6.0.0+, and MSVC 19.24+
#if defined(BUILTIN_NAN_SUPPORTED)
return __builtin_nan(num);
#else
// If __builtin_nan is not supported, use the standard library's nan which is not constexpr
std::nan(num); // I'm not sure I should even allow this as it prevents this from being used in static_asserts
#endif
}
} // namespace ccm
#ifdef BUILTIN_NAN_SUPPORTED
#undef BUILTIN_NAN_SUPPORTED
#endif
It can be helpful to represent edge cases for something like fmod where if NaN is passed it has specific outcomes.
fmod is not constexpr
It is for the fmod I am implementing.
I am implementing <cmath> from the ground up with everything being constexpr using C++17 and yes I am aware that C++ makes <cmath> constexpr in later versions but those versions are far to bleeding edge for my taste.
NaN is just a property of the IEEE754 standard where in a single precision float the first 9 bits are set to 1's so for example float NaN is 0xFF8XXXXX where the X's can be any number
#include <cmath>
int main()
{
constexpr double nanVal = NAN;
}
since C++11
/usr/include/math.h:98: note: this is the location of the previous definition
98 | # define NAN (__builtin_nanf (""))
|
double NaN is 0x7FF80000000000000 I believe
So if I were to return this value would the compiler treat it as a NaN or would it throw an exception?
The compiler would treat it as such
@abstract scaffold how about std::numeric_limits<T>::quiet_NaN()?
;compile ```cpp
unsigned a = 0xFFF00000;
float NaN = reinterpret_cast<float>(&a);
std::cout << NaN;
Program Output
-nan
grumpy_pig_skin | 42ms | c++ | x86-64 gcc 13.2 | godbolt.org
that is uint64_t not double
that's not gonna result in nan
I thought about that but it still provides the issue that it technically does not act the same as how std::nan would actually return.
Its pretty annoying for older compilers without the builtin.
constexpr floating point math isn't required to produce the same results as runtime floating point math
so that doesn't sound like a big deal
wait wut
for example, your compiler and the machine that you're running the code on may have different levels of precision with floating point arithmetic
so the same operations may result in different values in constexpr vs runtime
IEEE 754 64-bit floating point should be identical, what do you mean
the representation is the same, but computers aren't 100% precise in every floating point operation
bruh, 1.0/3.0 returns the exact same value every single time
try something like std::sin(1) on many machines and see if it always results in the same value
each machine has its internal sin implementation, why are you showing this
So then would something like this make more sense?
namespace ccm
{
template <typename T>
inline constexpr T nan(T num)
{
#if defined(BUILTIN_NAN_SUPPORTED)
return __builtin_nan(num);
#else
constexpr if(CHECK FOR CHAR TYPE)
{
if ( check if "" )
{
return std::numeric_limits<T>::quiet_nan;
} // other stuff
#endif
}
} // namespace ccm
#ifdef BUILTIN_NAN_SUPPORTED
#undef BUILTIN_NAN_SUPPORTED
#endif
std::nan takes a const char* not a T
This is true. I likely will change this to do the same, but you get what I mean by my suggestion?
it also seems like std::nan isn't constexpr in C++26
Yeah this was one of the few that was an outlier, I choose to only include it due to the fact that I found I could handle it as constexpr due to the builtin.
probably because it's described in terms of strto* functions = locale dependent I think?
Yeah that is likely the case as its one of the few <cmath> functions that don't really even use templates.
Technically GCC can use either a 80 bit float for long double or a 128 bit long double and some platforms enforce this by default. It really depends.
FLT_EVAL_METHOD being
as usual
Also I did not know that constexpr is actually allowed to have different outcomes from runtime. Why is this the case and is it deterministic?
because not all machines have 100% exact floating point math
so to allow cross compiling you effectively have to allow for this
I assumed but is it still deterministic for its respective machine or is it only specific for what ever the compiler decides the level of precision it will use?
Oh I see, so it chooses based on the platform its targeting?
it should be deterministic, unless you're using something non-standard like -Ofast
C also has some pragmas which can control how the implementation can adjust floating point operations, e.g FP_CONTRACT, FENV_ACCESS, and CX_LIMITED_RANGE in C17
Ok, that's really good to know. ^^
though FLT_EVAL_METHOD can troll you and make something like
double d=.1;
d==.1;
be false
True lol dunno what I thought i was doing there