1

C2x (as well as previous):

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.

Sample code (t0a.c)

#include <stdio.h>
#include <math.h>
#include <fenv.h>

#if _MSC_VER && ! __clang__ && ! __INTEL_COMPILER
#pragma fenv_access (on)
#else
#pragma STDC FENV_ACCESS ON
#endif

void print_fe_excepts_raised(void)
{
    printf("exceptions raised ");
    if (fetestexcept(FE_DIVBYZERO))  printf(" FE_DIVBYZERO");
    if (fetestexcept(FE_INEXACT))    printf(" FE_INEXACT");
    if (fetestexcept(FE_INVALID))    printf(" FE_INVALID");
    if (fetestexcept(FE_OVERFLOW))   printf(" FE_OVERFLOW");
    if (fetestexcept(FE_UNDERFLOW))     printf(" FE_UNDERFLOW");
    if (fetestexcept(FE_ALL_EXCEPT)==0) printf(" none");
    printf("\n");
}

int main(void)
{
    float f;

    feclearexcept(FE_ALL_EXCEPT);
    f = NAN;
    print_fe_excepts_raised();
    (void)f;
    return 0;
}

Invocations:

# msvc (version 19.29.30133 for x64)
$ cl t0a.c /std:c11 /Za /fp:strict && t0a.exe
exceptions raised  FE_INEXACT FE_INVALID FE_OVERFLOW

# clang on Windows (version 13.0.0)
$ clang t0a.c -std=c11 -pedantic -Wall -Wextra -ffp-model=strict && ./a.exe
exceptions raised  FE_INEXACT FE_INVALID FE_OVERFLOW

# gcc on Windows (version 11.2.0)
$ gcc t0a.c -std=c11 -pedantic -Wall -Wextra  && ./a.exe
exceptions raised  none

# gcc on Linux (version 11.2.0)
$ gcc t0a.c -std=c11 -pedantic -Wall -Wextra  && ./a.out
exceptions raised  none

# clang on Linux (version 13.0.0)
$ clang t0a.c -std=c11 -pedantic -Wall -Wextra -ffp-model=strict && ./a.out
exceptions raised  none

For msvc and clang on Windows: this is because:

C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt\corecrt_math.h:94:9
#define NAN ((float)(INFINITY * 0.0F))

C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt\corecrt_math.h:90:9
#define INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF))

C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt\corecrt_math.h:87:13
#define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow

Here we see that NAN "expands to a constant expression of type float representing a quiet NaN". Which implies that f = NAN may cause floating-point exceptions. However, f = NAN is usually seen as "writing to memory". Hence, people may wonder: "how writing to memory may cause raising floating-point exceptions?".

Lundin
  • 195,001
  • 40
  • 254
  • 396
pmor
  • 5,392
  • 4
  • 17
  • 36
  • Preliminarily, the fact that a macro expands to a constant expression does not require its value to be computed at compile time. If the expansion contains operators and if the context permits, then the operations involved *may* be evaluated at runtime. In that case, it can be the computation of the floating-point NaN that causes an FP exception to be raised, as opposed to that being an effect of the assignment of the result. – John Bollinger Nov 11 '21 at 14:15
  • 2
    So Microsoft's headers define a NAN but it's not quiet. You found a bit where they deviate from the standard. Let's ignore wondering people and focus on your question. Hmm, what is your question actually? –  Nov 11 '21 at 14:23
  • Actually, the value produced by the expression is most likely a quiet NaN. [The whole thing seems fairly complicated.](https://stackoverflow.com/questions/18118408/what-is-the-difference-between-quiet-nan-and-signaling-nan) –  Nov 11 '21 at 14:38

1 Answers1

3

For the record, this ...

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.

... is C17 7.12/5, and it probably has the same or similar numbering in C2x.


Updated

The fact that when used with MSVC or Clang on Windows, your test program causes the FE_INVALID FP exception to be raised suggests that the combination of

  • Microsoft's C standard library and runtime environment with
  • the MSVC and Clang compilers and
  • the options you are specifying to those

is causing a signaling NaN to be generated and used as an arithmetic operand. I would agree that that is an unexpected result, probably indicating that these combinations fail to fully conform to the C language specification in this area.

has nothing to do with whether the resulting NaN is a quiet or a signaling one. The misconception there is that the FE_INVALID flag would be raised only as a consequence of generating or operating on a signaling NaN. That is not the case.

For one thing, IEEE-754 does not define any case in which a signaling NaN is generated. All defined operations that produce an NaN produce a quiet NaN, including operations in which one operand is a signaling NaN (so MSVC and Clang on Windows almost certainly do produce a quiet NaN as the value of the NAN macro). Most operations with at least one signaling NaN as an operand do, by default, cause the FE_INVALID flag to be raised, but that is not the usual reason for that flag to be raised.

Rather, under default exception handling, the FE_INVALID flag is raised simply because of a request to compute an operation with no defined result, such as infinity times 0. The result will be a quiet NaN. Note that this does not include operations with at least one NaN operand, which do have a defined result: a quiet NaN in many cases, unordered / false for comparisons, and other results in a few cases.

With that for context, it is important to recognize that just because NAN expands to a constant expression (in a conforming C implementation) does not mean that the value of that expression is computed at compile time. Indeed, given the specifications for MSVC's and Clang's strict fp modes, I would expect those modes to disable most, if not all, compile-time computation of FP expressions (or at minimum to propogate FP status flags as if the computations were performed at run time).

Thus, raising FE_INVALID is not necessarily an effect of the assignment in f = NAN. If (as in Microsoft's C standard library) NAN expands to an expression involving arithmetic operations then the exception should be raised as a result of the evaluating that expression, notwithstanding that the resulting NaN is quiet. At least in implementations that claim full conformance with IEC 60559 by defining the __STDC_IEC_559__ feature-test macro.

Therefore, although I will not dispute that

people may wonder: "how writing to memory may cause raising floating-point exceptions?".

, no convincing evidence has been presented to suggest that such causation has been observed.

Nevertheless, the value represented by a particular appearance of NAN in an expression that is evaluated has some kind of physical manifestation. It is plausible for that to be in an FPU register, and storing a signaling NaN from an FPU register to memory indeed could cause a FP exception to be raised on some architectures.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Re: _I would expect ..._: According to tests, they indeed disable most, if not all, compile-time computation of FP expressions. However, this is [not mentioned in the documentation](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170). – pmor Nov 11 '21 at 18:01
  • It may not be mentioned explicitly in the documentation, @pmor, but it is a predictable requirement for implementing the behavior that *is* documented. In any case, it is not safe or reasonable to assume that just because something can be done, it will be done. – John Bollinger Nov 11 '21 at 18:02
  • FYI: According to tests clang under `-ffp-model=strict` keeps computing FP expressions at compile time. – pmor Nov 11 '21 at 18:10
  • If that's indeed so, @pmor, then Clang must be going to a lot of trouble to also capture the the resulting FP status at compile time and re-apply it at run time. Which, of course, is a valid way of doing it, and that also contradicts the premise that FP exceptions arising from the computation of the `NAN` expression should not be visible at run time. – John Bollinger Nov 11 '21 at 18:26
  • Please (try to) address https://stackoverflow.com/questions/67972598/ieee-754-why-predicates-and-are-not-signaling. – pmor Nov 11 '21 at 18:55
  • 1
    Re: _Clang must be going to a lot of trouble_: also doing FP computations at compile time rather than at run time can be error prone because [logic implementing FP operations in compiler's front end](https://github.com/llvm-mirror/llvm/blob/2c4ca6832fa6b306ee6a7010bfb80a3f2596f824/lib/Support/APFloat.cpp) (usually arbitrary precision) usually is not formally verified (and, hence, may be subject of errors), while the logic implementing FP operations in Intel CPUs [is formally verified](https://www.cl.cam.ac.uk/~jrh13/slides/nijmegen-21jun02/slides.pdf) (and, hence, free of errors). – pmor Nov 14 '21 at 12:54
  • Re: _to disable most, if not all, compile-time computation of FP expressions_. Btw, because of that `float x = 0.0f + 0.0f;` placed at file scope leads to `error C2099: initializer is not a constant` despite the fact that `0.0f + 0.0f` does not lead to raising of floating-point exceptions nor may produce different results under non-default rounding modes. How can you comment on this? – pmor Nov 22 '21 at 10:51