9

Recent MSVC versions don't seem to treat NAN as a constant anymore. The new definition appears to be (__ucrt_int_to_float(0x7FC00000)). The old one was (-(float)(((float)(1e+300 * 1e+300)) * 0.0F)).

This causes code like the following to fail to compile with error C2099: initializer is not a constant:

#include <math.h>

double x[] = { 1.0, NAN };

Unfortunately, I don't have direct access to MSVC and I am dealing with this through GitHub CI.

Questions:

  • Is this a bug in MSVC or valid behaviour?
  • What is a robust workaround that won't break the code with other compilers?

Apparently, MSVC also doesn't accept 0.0 / 0.0. The code double f() { return 0.0 / 0.0; } fails with error C2124: divide or mod by zero

9
  • GodBolt appears to have no problem with x64 MSVC v19 latest: godbolt.org/z/obbsb6znn Commented Nov 16, 2024 at 12:42
  • I know, it doesn't have the latest MSVC. It seems this affects 17.12, but not 17.11. Godbolt has 17.10. Commented Nov 16, 2024 at 12:43
  • 1
    Found some discussion: github.com/protocolbuffers/protobuf/issues/17308 and developercommunity.visualstudio.com/t/… Commented Nov 16, 2024 at 12:44
  • NAN must be a constant expression. If it isn't, then that's a compiler/library bug. Commented Nov 16, 2024 at 12:46
  • 1
    Also, this is obviously not a duplicate. The question is entirely different. Please don't abuse closing powers. This is exactly why StackOverflow is getting such a bad reputation recently. Commented Nov 16, 2024 at 22:49

3 Answers 3

4

This seems to be a bug in Windows SDK version 10.0.26100.0, which defines NAN as a call to __ucrt_int_to_float() instead of as a constant. It is being tracked here.

As a workaround one can define _UCRT_NOISY_NAN to enable the legacy definition of the NAN macro, which can be seen here.

Update: according to Microsoft it’s fixed in SDK version 10.0.26100.3916.

Sign up to request clarification or add additional context in comments.

Comments

2

This is a bug in MSVC.

The NAN macro is required to be a constant expression. Such expressions are allowed in file scope initializers as they can be evaluated at compile time.

This is mandated in section 7.12p6 of the C99 standard:

The macro

NAN

is defined if and only if the implementation supports quiet NaNs for the float type. It expands to a constant expression of type float representing a quiet NaN.

The same language exists in C11 and C23.

This isn't the first time that MSVC has exhibited non standard compliant behavior.

To work around this, you can check the MSVC version by inspecting the _MSC_VER macro. For MSVC 17.12, the internal version number is 19.42 which translates to a macro value of 1942. Based on this, you can redefine NAN to what is was previously.

#include <math.h>

#ifdef _MSC_VER
# if _MSC_VER >= 1942
#   undef NAN
#   define NAN (-(float)(((float)(1e+300 * 1e+300)) * 0.0F))
# endif
#endif

4 Comments

MSVC is a C++ compiler, not a C compiler. I don't think it makes any claims to be C compliant.
@MarkRansom: It's marketed as a "C/C++ compiler" and there's an entire documentation section on using it for C. There's also a specific option for C11/C17 compliance.
@NateEldredge thanks for the link, I wasn't aware they were even making an effort. This site is littered with examples of MSVC not conforming 100% to C standards.
@MarkRansom This is from memory, so details might be wrong, but I think before VS 2015 they didn't make any improvements to C that wasn't needed for C++, and there was little to no support for C99. I remember having a lot of workarounds in igraph. VS 2015 made major improvements. It doesn't have full language support, but the standard library is in a very good shape. After that, it's quite shocking to see this NAN messup.
1

In reference to the workaround proposed by dbush, I wouldn't solely check against _MSC_VER. The SDK version is something different than the compiler version. And the SDK doesn't provide a version macro.

Here's a refined workaround taking the implementation details of the 10.0.22000.0, 10.0.26100.0 and 10.0.26100.3916 versions into account:

// Guard that best-effort implements (SDK >= 10.0.26100.0) && (SDK < 10.0.26100.3916) based on the
// SDK implementations. However, guard may have to be refined once newer SDKs become available.
#if defined(_MSC_VER)
    #if !defined(_UCRT_NOISY_NAN) && !defined(__midl) && !defined(__cplusplus) // Issue only occurs under this condition.
        #if !defined(_UCRT_LEGACY_INFINITY) && defined(_HUGE_ENUF)             // Activate the patch for 10.0.26100.0 and above...
            #if !defined(_UCRT_HAS_BUILTIN)                                    // ...but no longer for 10.0.26100.3916 and above.
                #undef INFINITY
                #define INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF))

                #undef NAN
                #define NAN ((float)(INFINITY * 0.0F))
            #endif
        #endif
    #endif
#endif

But keep in mind this is just one of at least three potential workarounds:

  • Redefine INFINITY and NAN as proposed above.

  • Define _UCRT_NOISY_NAN before including <math.h> and other math related headers.

  • Define _UCRT_NOISY_NAN in the project's/build's settings.

The latter two are hinted at by the currently accepted solution by Frederik. But if for some reason _UCRT_NOISY_NAN cannot or doesn't want to be defined, the above workaround may be preferred.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.