Context: for the following sample code different compilers produce different results wrt raising of floating-point exceptions.
Question: how to explain the difference?
Sample code (t125.c):
#include <fenv.h>
#include <stdio.h>
#include <math.h>
#include <stdint.h>
#include <string.h>
#if _MSC_VER && ! __clang__
#pragma fenv_access (on)
#else
#pragma STDC FENV_ACCESS ON
#endif
void show_fe_exceptions(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");
}
/*
* based on:
* https://github.com/google/flatbuffers/blob/4133a39df80546ff5269894a3961c7069fcd45d0/tests/test.cpp#L693
*/
#if TID == 32
_Bool is_quiet_nan(float v)
#elif TID == 64
_Bool is_quiet_nan(double v)
#else
#error "unknown TID"
#endif
{
#if TID == 32
uint32_t qnan_base = 0x7FC00000u;
uint32_t u;
#elif TID == 64
uint64_t qnan_base = 0x7FF8000000000000ul;
uint64_t u;
#else
#error "unknown TID"
#endif
_Static_assert(sizeof(v) == sizeof(u), "unexpected");
memcpy(&u, &v, sizeof(v));
return ((u & qnan_base) == qnan_base);
}
#if TID == 32
float xfma(float x, float y, float z) { return fmaf(x, y, z); }
float xsqrt(float x) { return sqrtf(x); }
#elif TID == 64
double xfma(double x, double y, double z) { return fma(x, y, z); }
double xsqrt(double x) { return sqrt(x); }
#else
#error "unknown TID"
#endif
int main(void)
{
volatile _Bool b;
#if TID == 32
QUAL float f = NAN;
#elif TID == 64
QUAL double f = NAN;
#else
#error "unknown TID"
#endif
printf("f is %f\n", f);
printf("f is quiet nan %d\n", is_quiet_nan(f));
printf("NAN is quiet nan %d\n", is_quiet_nan(NAN));
#define TEST(expr) \
feclearexcept(FE_ALL_EXCEPT); \
printf("%-25s => ", #expr); \
b = (expr) != 0; \
show_fe_exceptions();
printf("\n");
printf("C11, 5.2.4.2.2, p3:\n");
printf("> A quiet NaN propagates through almost every arithmetic\n");
printf("> operation without raising a floating-point exception; ...\n");
TEST(f + f);
TEST(f - f);
TEST(f * f);
TEST(f / f);
TEST(xsqrt(f));
TEST(xfma(f, f, f));
TEST(NAN + NAN);
TEST(NAN - NAN);
TEST(NAN * NAN);
TEST(NAN / NAN);
TEST(xsqrt(NAN));
TEST(xfma(NAN, NAN, NAN));
printf("\n");
printf("IEEE 754, 5.11, p4:\n");
printf("> Programs that explicitly take account of the possibility of\n");
printf("> quiet NaN operands may use the unordered-quiet predicates in\n");
printf("> Table 5.3 which do not signal such an invalid operation exception.\n");
TEST(f == f);
TEST(f != f);
TEST(f >= f);
TEST(f <= f);
TEST(f > f);
TEST(f < f);
TEST(NAN == NAN);
TEST(NAN != NAN);
TEST(NAN >= NAN);
TEST(NAN <= NAN);
TEST(NAN > NAN);
TEST(NAN < NAN);
return b ? 1 : 0;
}
Invocations (attention: many results):
# clang
# const float (baseline)
$ clang t125.c -std=c11 -ffp-model=strict -Wall -Wextra -pedantic -DQUAL=const -O3 -DTID=32 && ./a.exe
t125.c:10:14: warning: pragma STDC FENV_ACCESS ON is not supported, ignoring pragma [-Wunknown-pragmas]
#pragma STDC FENV_ACCESS ON
^
1 warning generated.
f is -nan(ind)
f is quiet nan 1
NAN is quiet nan 1
C11, 5.2.4.2.2, p3:
> A quiet NaN propagates through almost every arithmetic
> operation without raising a floating-point exception; ...
f + f => exceptions raised: none
f - f => exceptions raised: none
f * f => exceptions raised: none
f / f => exceptions raised: none
xsqrt(f) => exceptions raised: none
xfma(f, f, f) => exceptions raised: none
NAN + NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN - NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN * NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN / NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
xsqrt(NAN) => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
xfma(NAN, NAN, NAN) => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
IEEE 754, 5.11, p4:
> Programs that explicitly take account of the possibility of
> quiet NaN operands may use the unordered-quiet predicates in
> Table 5.3 which do not signal such an invalid operation exception.
f == f => exceptions raised: none
f != f => exceptions raised: none
f >= f => exceptions raised: FE_INVALID
f <= f => exceptions raised: FE_INVALID
f > f => exceptions raised: FE_INVALID
f < f => exceptions raised: FE_INVALID
NAN == NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN != NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN >= NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN <= NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN > NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN < NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
$ get_cmd() { cmd="clang t125.c -std=c11 -ffp-model=strict -Wall -Wextra -pedantic \
-O3 -Wfatal-errors -DQUAL=$QUAL1 -DTID=$TID1 && ./a.exe > out1 && clang t125.c \
-std=c11 -ffp-model=strict -Wall -Wextra -pedantic -O3 -Wfatal-errors -DQUAL=$QUAL2\
-DTID=$TID2 && ./a.exe > out2 && diff out1 out2" ; }
# note: below we do not count `warning: pragma STDC FENV_ACCESS ON is not supported`
# const float vs const double
$ QUAL1=const TID1=32 QUAL2=const TID2=64 get_cmd ; eval $cmd
<nothing>
# const float vs volatile float
$ QUAL1=const TID1=32 QUAL2=volatile TID2=32 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 72a12bf..d87d79e 100644
--- a/out1
+++ b/out2
@@ -1,4 +1,4 @@
-f is -nan(ind)
+f is nan^M
f is quiet nan 1
NAN is quiet nan 1
# const float vs volatile double
$ QUAL1=const TID1=32 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 72a12bf..d87d79e 100644
--- a/out1
+++ b/out2
@@ -1,4 +1,4 @@
-f is -nan(ind)
+f is nan^M
f is quiet nan 1
NAN is quiet nan 1
# const double vs volatile float
$ QUAL1=const TID1=64 QUAL2=volatile TID2=32 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 72a12bf..d87d79e 100644
--- a/out1
+++ b/out2
@@ -1,4 +1,4 @@
-f is -nan(ind)
+f is nan^M
f is quiet nan 1
NAN is quiet nan 1
# const double vs volatile double
$ QUAL1=const TID1=64 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 72a12bf..d87d79e 100644
--- a/out1
+++ b/out2
@@ -1,4 +1,4 @@
-f is -nan(ind)
+f is nan^M
f is quiet nan 1
NAN is quiet nan 1
# volatile float vs volatile double
$ QUAL1=volatile TID1=32 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
<nothing>
# gcc
# const float (baseline)
$ gcc t125.c -std=c11 -Wall -Wextra -pedantic -O3 -DQUAL=const -DTID=32 && ./a.exe
t125.c:10: warning: ignoring ‘#pragma STDC FENV_ACCESS’ [-Wunknown-pragmas]
10 | #pragma STDC FENV_ACCESS ON
|
f is nan
f is quiet nan 1
NAN is quiet nan 1
C11, 5.2.4.2.2, p3:
> A quiet NaN propagates through almost every arithmetic
> operation without raising a floating-point exception; ...
f + f => exceptions raised: none
f - f => exceptions raised: none
f * f => exceptions raised: none
f / f => exceptions raised: none
xsqrt(f) => exceptions raised: none
xfma(f, f, f) => exceptions raised: none
NAN + NAN => exceptions raised: none
NAN - NAN => exceptions raised: none
NAN * NAN => exceptions raised: none
NAN / NAN => exceptions raised: none
xsqrt(NAN) => exceptions raised: none
xfma(NAN, NAN, NAN) => exceptions raised: none
IEEE 754, 5.11, p4:
> Programs that explicitly take account of the possibility of
> quiet NaN operands may use the unordered-quiet predicates in
> Table 5.3 which do not signal such an invalid operation exception.
f == f => exceptions raised: none
f != f => exceptions raised: none
f >= f => exceptions raised: none
f <= f => exceptions raised: none
f > f => exceptions raised: none
f < f => exceptions raised: none
NAN == NAN => exceptions raised: none
NAN != NAN => exceptions raised: none
NAN >= NAN => exceptions raised: none
NAN <= NAN => exceptions raised: none
NAN > NAN => exceptions raised: none
NAN < NAN => exceptions raised: none
$ get_cmd() { cmd="gcc t125.c -std=c11 -Wall -Wextra -pedantic -O3 -DQUAL=$QUAL1\
-DTID=$TID1 && ./a.exe > out1 && gcc t125.c -std=c11 -Wall -Wextra -pedantic -O3\
-DQUAL=$QUAL2 -DTID=$TID2 && ./a.exe > out2 && diff out1 out2" ; }
# note: below we do not count `warning: pragma STDC FENV_ACCESS ON is not supported`
# const float vs const double
$ QUAL1=const TID1=32 QUAL2=const TID2=64 get_cmd ; eval $cmd
<nothing>
# const float vs volatile float
$ QUAL1=const TID1=32 QUAL2=volatile TID2=32 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 9ef46f1..65f7158 100644
--- a/out1
+++ b/out2
@@ -24,10 +24,10 @@ IEEE 754, 5.11, p4:
> Table 5.3 which do not signal such an invalid operation exception.
f == f => exceptions raised: none
f != f => exceptions raised: none
-f >= f => exceptions raised: none
-f <= f => exceptions raised: none
-f > f => exceptions raised: none
-f < f => exceptions raised: none
+f >= f => exceptions raised: FE_INVALID
+f <= f => exceptions raised: FE_INVALID
+f > f => exceptions raised: FE_INVALID
+f < f => exceptions raised: FE_INVALID
NAN == NAN => exceptions raised: none
NAN != NAN => exceptions raised: none
NAN >= NAN => exceptions raised: none
# const float vs volatile double
$ QUAL1=const TID1=32 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 9ef46f1..65f7158 100644
--- a/out1
+++ b/out2
@@ -24,10 +24,10 @@ IEEE 754, 5.11, p4:
> Table 5.3 which do not signal such an invalid operation exception.
f == f => exceptions raised: none
f != f => exceptions raised: none
-f >= f => exceptions raised: none
-f <= f => exceptions raised: none
-f > f => exceptions raised: none
-f < f => exceptions raised: none
+f >= f => exceptions raised: FE_INVALID
+f <= f => exceptions raised: FE_INVALID
+f > f => exceptions raised: FE_INVALID
+f < f => exceptions raised: FE_INVALID
NAN == NAN => exceptions raised: none
NAN != NAN => exceptions raised: none
NAN >= NAN => exceptions raised: none
# const double vs volatile float
$ QUAL1=const TID1=64 QUAL2=volatile TID2=32 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 9ef46f1..65f7158 100644
--- a/out1
+++ b/out2
@@ -24,10 +24,10 @@ IEEE 754, 5.11, p4:
> Table 5.3 which do not signal such an invalid operation exception.
f == f => exceptions raised: none
f != f => exceptions raised: none
-f >= f => exceptions raised: none
-f <= f => exceptions raised: none
-f > f => exceptions raised: none
-f < f => exceptions raised: none
+f >= f => exceptions raised: FE_INVALID
+f <= f => exceptions raised: FE_INVALID
+f > f => exceptions raised: FE_INVALID
+f < f => exceptions raised: FE_INVALID
NAN == NAN => exceptions raised: none
NAN != NAN => exceptions raised: none
NAN >= NAN => exceptions raised: none
# const double vs volatile double
$ QUAL1=const TID1=64 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
diff --git a/out1 b/out2
index 9ef46f1..65f7158 100644
--- a/out1
+++ b/out2
@@ -24,10 +24,10 @@ IEEE 754, 5.11, p4:
> Table 5.3 which do not signal such an invalid operation exception.
f == f => exceptions raised: none
f != f => exceptions raised: none
-f >= f => exceptions raised: none
-f <= f => exceptions raised: none
-f > f => exceptions raised: none
-f < f => exceptions raised: none
+f >= f => exceptions raised: FE_INVALID
+f <= f => exceptions raised: FE_INVALID
+f > f => exceptions raised: FE_INVALID
+f < f => exceptions raised: FE_INVALID
NAN == NAN => exceptions raised: none
NAN != NAN => exceptions raised: none
NAN >= NAN => exceptions raised: none
# volatile float vs volatile double
$ QUAL1=volatile TID1=32 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
<nothing>
# cl
# const float (baseline)
$ cl t125.c /std:c11 /fp:strict /DQUAL=const /DTID=32 && t125
f is nan
f is quiet nan 1
NAN is quiet nan 1
C11, 5.2.4.2.2, p3:
> A quiet NaN propagates through almost every arithmetic
> operation without raising a floating-point exception; ...
f + f => exceptions raised: none
f - f => exceptions raised: none
f * f => exceptions raised: none
f / f => exceptions raised: none
xsqrt(f) => exceptions raised: none
xfma(f, f, f) => exceptions raised: none
NAN + NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN - NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN * NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN / NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
xsqrt(NAN) => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
xfma(NAN, NAN, NAN) => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
IEEE 754, 5.11, p4:
> Programs that explicitly take account of the possibility of
> quiet NaN operands may use the unordered-quiet predicates in
> Table 5.3 which do not signal such an invalid operation exception.
f == f => exceptions raised: none
f != f => exceptions raised: none
f >= f => exceptions raised: FE_INVALID
f <= f => exceptions raised: FE_INVALID
f > f => exceptions raised: FE_INVALID
f < f => exceptions raised: FE_INVALID
NAN == NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN != NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN >= NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN <= NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN > NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
NAN < NAN => exceptions raised: FE_INEXACT FE_INVALID FE_OVERFLOW
$ get_cmd() { cmd="cl t125.c /std:c11 /fp:strict /DQUAL=$QUAL1 /DTID=$TID1\
&& ./t125.exe > out1 && cl t125.c /std:c11 /fp:strict /DQUAL=$QUAL2 /DTID=$TID2\
&& ./t125.exe > out2 && diff out1 out2" ; }
# const float vs const double
$ QUAL1=const TID1=32 QUAL2=const TID2=64 get_cmd ; eval $cmd
<nothing>
# const float vs volatile float
$ QUAL1=const TID1=32 QUAL2=volatile TID2=32 get_cmd ; eval $cmd
<nothing>
# const float vs volatile double
$ QUAL1=const TID1=32 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
<nothing>
# const double vs volatile float
$ QUAL1=const TID1=64 QUAL2=volatile TID2=32 get_cmd ; eval $cmd
<nothing>
# const double vs volatile double
$ QUAL1=const TID1=64 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
<nothing>
# volatile float vs volatile double
$ QUAL1=volatile TID1=32 QUAL2=volatile TID2=64 get_cmd ; eval $cmd
<nothing>
$ gcc --version
gcc (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
clang --version
clang version 11.0.1
Target: x86_64-pc-windows-msvc
Thread model: posix
$ cl
Microsoft (R) C/C++ Optimizing Compiler Version 19.28.29913 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
Update: It turns out that __STDC_IEC_559__
value (if defined) depends of the host OS. For example, both gcc 10.2.0 (with -frounding-math -fsignaling-nans
) and clang 11.0.1 (with -ffp-model=strict
) running on Windows 10 (version 2004) do not define __STDC_IEC_559__
to 1. Hence, switch host OS from Windows 10 to Ubuntu 20.04 LTS, then redo the tests: https://pastebin.com/NJwcyVkm (too much content in the body, have to use external content hosting service).