首页 > 解决方案 > NAN 用于算术/比较运算:编译器之间的不同结果 wrt 引发浮点异常:如何解释?

问题描述

上下文:对于以下示例代码,不同的编译器会在引发浮点异常时产生不同的结果。

问题:如何解释差异?

示例代码(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;
}

调用(注意:许多结果):

# 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.

更新:事实证明该__STDC_IEC_559__值(如果已定义)取决于主机操作系统。例如,在 Windows 10(版本 2004)上运行的 gcc 10.2.0(带-frounding-math -fsignaling-nans)和 clang 11.0.1(带-ffp-model=strict)都没有定义__STDC_IEC_559__为 1。因此,将主机操作系统从 Windows 10 切换到 Ubuntu 20.04 LTS,然后重做测试: https://pastebin.com/NJwcyVkm(正文内容太多,必须使用外部内容托管服务)。

标签: cnanieee-754c11floating-point-exceptions

解决方案


推荐阅读