#How to represent a constexpr NaN with older C++ compilers

50 messages · Page 1 of 1 (latest)

abstract scaffold
#

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?

quasi frostBOT
#

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.

fluid flint
abstract scaffold
#
/*
 * 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

abstract scaffold
abstract scaffold
abstract scaffold
# fluid flint fmod is not constexpr

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.

haughty solstice
#

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

fluid flint
#
/usr/include/math.h:98: note: this is the location of the previous definition
   98 | #  define NAN (__builtin_nanf (""))
      | 
haughty solstice
#

double NaN is 0x7FF80000000000000 I believe

abstract scaffold
haughty solstice
hidden belfry
#

@abstract scaffold how about std::numeric_limits<T>::quiet_NaN()?

haughty solstice
#

;compile ```cpp
unsigned a = 0xFFF00000;
float NaN = reinterpret_cast<float>(&a);
std::cout << NaN;

topaz geodeBOT
#
Program Output
-nan
fluid flint
#

that is uint64_t not double

hidden belfry
abstract scaffold
#

Its pretty annoying for older compilers without the builtin.

hidden belfry
#

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

hidden belfry
# fluid flint 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

fluid flint
hidden belfry
#

the representation is the same, but computers aren't 100% precise in every floating point operation

fluid flint
hidden belfry
#

try something like std::sin(1) on many machines and see if it always results in the same value

fluid flint
abstract scaffold
hidden belfry
#

std::nan takes a const char* not a T

abstract scaffold
#

This is true. I likely will change this to do the same, but you get what I mean by my suggestion?

hidden belfry
#

it also seems like std::nan isn't constexpr in C++26

abstract scaffold
hidden belfry
#

probably because it's described in terms of strto* functions = locale dependent I think?

abstract scaffold
abstract scaffold
hidden belfry
#

FLT_EVAL_METHOD being trolol as usual

abstract scaffold
hidden belfry
#

so to allow cross compiling you effectively have to allow for this

abstract scaffold
abstract scaffold
hidden belfry
#

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

abstract scaffold
hidden belfry
haughty solstice