【问题标题】:Why conversion (unsigned long long)DBL_MAX (or FLT_MAX) causes raising of FE_INEXACT as well?为什么转换(无符号长长)DBL_MAX(或 FLT_MAX)也会导致 FE_INEXACT 升高?
【发布时间】:2021-03-01 16:43:05
【问题描述】:

代码(t1.c):

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

#if _MSC_VER
#pragma fenv_access (on)
#else
#pragma STDC FENV_ACCESS ON
#endif


void print_fpe()
{
    int fpe = fetestexcept(FE_ALL_EXCEPT);
    printf("current exceptions raised:");
    if (fpe & FE_DIVBYZERO)       printf(" FE_DIVBYZERO");
    if (fpe & FE_INEXACT)         printf(" FE_INEXACT");
    if (fpe & FE_INVALID)         printf(" FE_INVALID");
    if (fpe & FE_OVERFLOW)        printf(" FE_OVERFLOW");
    if (fpe & FE_UNDERFLOW)       printf(" FE_UNDERFLOW");
    if ((fpe & FE_ALL_EXCEPT)==0) printf(" none");
}

volatile double d = DBL_MAX;
volatile float f = FLT_MAX;
volatile signed long long ll;
volatile signed long l;
volatile signed int i;
volatile signed short s;
volatile signed char c;
volatile unsigned long long ull;
volatile unsigned long ul;
volatile unsigned int ui;
volatile unsigned short us;
volatile unsigned char uc;

#define TEST(dst, type, src)         \
    feclearexcept(FE_ALL_EXCEPT);    \
    dst = (type)(src);               \
    print_fpe();                     \
    printf(" line %u\n", __LINE__);

int main(void)
{
    TEST(ll, signed long long, d);
    TEST(l, signed long, d);
    TEST(i, signed int, d);
    TEST(s, signed short, d);
    TEST(c, signed char, d);
    TEST(ll, signed long long, f);
    TEST(l, signed long, f);
    TEST(i, signed int, f);
    TEST(s, signed short, f);
    TEST(c, signed char, f);
    TEST(ull, unsigned long long, d); // line 55
    TEST(ul, unsigned long, d);
    TEST(ui, unsigned int, d);
    TEST(us, unsigned short, d);
    TEST(uc, unsigned char, d);
    TEST(ull, unsigned long long, f); // line 60
    TEST(ul, unsigned long, f);
    TEST(ui, unsigned int, f);
    TEST(us, unsigned short, f);
    TEST(uc, unsigned char, f);
    return 0;
}

调用和结果:

$ cl t1.c && t1
current exceptions raised: FE_INVALID line 45
current exceptions raised: FE_INVALID line 46
current exceptions raised: FE_INVALID line 47
current exceptions raised: FE_INVALID line 48
current exceptions raised: FE_INVALID line 49
current exceptions raised: FE_INVALID line 50
current exceptions raised: FE_INVALID line 51
current exceptions raised: FE_INVALID line 52
current exceptions raised: FE_INVALID line 53
current exceptions raised: FE_INVALID line 54
current exceptions raised: FE_INEXACT FE_INVALID line 55
current exceptions raised: FE_INVALID line 56
current exceptions raised: FE_INVALID line 57
current exceptions raised: FE_INVALID line 58
current exceptions raised: FE_INVALID line 59
current exceptions raised: FE_INEXACT FE_INVALID line 60
current exceptions raised: FE_INVALID line 61
current exceptions raised: FE_INVALID line 62
current exceptions raised: FE_INVALID line 63
current exceptions raised: FE_INVALID line 64

$ clang t1.c && ./a.exe
t1.c:8:14: warning: pragma STDC FENV_ACCESS ON is not supported, ignoring pragma [-Wunknown-pragmas]
#pragma STDC FENV_ACCESS ON
             ^
1 warning generated.
current exceptions raised: FE_INVALID line 45
current exceptions raised: FE_INVALID line 46
current exceptions raised: FE_INVALID line 47
current exceptions raised: FE_INVALID line 48
current exceptions raised: FE_INVALID line 49
current exceptions raised: FE_INVALID line 50
current exceptions raised: FE_INVALID line 51
current exceptions raised: FE_INVALID line 52
current exceptions raised: FE_INVALID line 53
current exceptions raised: FE_INVALID line 54
current exceptions raised: FE_INEXACT FE_INVALID line 55
current exceptions raised: FE_INEXACT FE_INVALID line 56
current exceptions raised: FE_INVALID line 57
current exceptions raised: FE_INVALID line 58
current exceptions raised: FE_INVALID line 59
current exceptions raised: FE_INEXACT FE_INVALID line 60
current exceptions raised: FE_INEXACT FE_INVALID line 61
current exceptions raised: FE_INVALID line 62
current exceptions raised: FE_INVALID line 63
current exceptions raised: FE_INVALID line 64

$ gcc t1.c && ./a.exe
current exceptions raised: FE_INVALID line 45
current exceptions raised: FE_INVALID line 46
current exceptions raised: FE_INVALID line 47
current exceptions raised: FE_INVALID line 48
current exceptions raised: FE_INVALID line 49
current exceptions raised: FE_INVALID line 50
current exceptions raised: FE_INVALID line 51
current exceptions raised: FE_INVALID line 52
current exceptions raised: FE_INVALID line 53
current exceptions raised: FE_INVALID line 54
current exceptions raised: FE_INEXACT FE_INVALID line 55
current exceptions raised: FE_INEXACT FE_INVALID line 56
current exceptions raised: FE_INVALID line 57
current exceptions raised: FE_INVALID line 58
current exceptions raised: FE_INVALID line 59
current exceptions raised: FE_INEXACT FE_INVALID line 60
current exceptions raised: FE_INEXACT FE_INVALID line 61
current exceptions raised: FE_INVALID line 62
current exceptions raised: FE_INVALID line 63
current exceptions raised: FE_INVALID line 64

问题:为什么转换(unsigned long long)DBL_MAX(或FLT_MAX)也会引起FE_INEXACT的提升?

【问题讨论】:

  • 类型unsigned long long 不能包含DBL_MAX(或FLT_MAX)的整数部分或其小数部分。
  • @WeatherVane:unsigned longunsigned 都不能代表整数部分。为什么会有差异? (顺便说一句,任何整数类型都可以表示DBL_MAXFLT_MAX 的小数部分,因为它们是零。)

标签: c exception floating-point language-lawyer ieee-754


【解决方案1】:

我想您正在 x86 上对此进行测试,因为这就是我看到您描述的行为的地方。 Example。这是底层的解释。

在 x86-64 上,gcc 至少使用cvttsd2si 指令将大多数浮点数转换为整数,该指令将双精度浮点数转换为 32 位或 64 位 有符号 整数,如果结果超出范围,则引发“无效”异常。该指令可用于转换为任何有符号整数类型,也可用于转换为 32 位或更低的无符号整数类型 - 例如,可以通过转换为有符号 64 位并丢弃高位来转换为无符号 32 位。

但这不适用于转换为无符号 64 位,因为输入可能是一个不适合有符号 64 位但适合无符号 64 位的数字,而 x86 没有指令可以做到这一点直接转换。因此,需要一些额外的算术,正是这些额外的指令产生了“不精确”的异常。 (具体来说,它会使用subsd 从输入中减去(double)LLONG_MAX,这确实会在输入为DBL_MAX 时导致精度损失。)

请参阅Unsigned 64-bit to double conversion: why this algorithm from g++,了解 gcc 为尽可能高效地执行此操作而采取的各种体操示例。

请注意,在 x86-64 上,您实际上也会看到 FP_INEXACT 转换为 unsigned long,因为它与 unsigned long long 相同。我得到了您在 x86-32 上观察到的确切行为,其中 unsigned long long 是唯一适用的 64 位类型。这种情况下的代码有点复杂,如果您真的感兴趣,我会留给您通读程序集。

相比之下,当我在 AArch64 上运行此代码时,所有行都简单地给出FE_INVALID。这是因为 AArch64 确实有一条专用指令将浮点数转换为无符号 64 位 (fcvtzu),因此没有可能涉及不精确结果的进一步算法。

【讨论】:

  • 谢谢! 1) 这些产生“不精确”异常的附加指令:究竟是哪些附加指令? 2) 仅供参考:我正在处理的硬件有float-to-integer 指令,只有在inexact &amp;&amp; ! invalid 时才会引发不精确的异常。 IE。不可能同时提高 FE_INEXACTFE_INVALID
  • @pmor: (1) 在 x86-64 上稍微简单一些,其中提高“不精确”的指令是来自 DBL_MAXsubsd(double)ULLONG_MAX。请参阅godbolt.org/z/GrfGdT,程序集的第 120 行。恐怕我在回答中有些混淆了 x86-32 和 x86-64; x86-32 上的代码更复杂,由于缺少 64 位整数指令,并且使用 x87 指令。 x86-64 的行为与我描述的一样,但也会在转换为 unsigned long 时显示 FP_INEXACT(因为它是无符号的 64 位,就像 unsigned long long 一样)。
  • @pmor: (2) 那到底是什么硬件?我同意 x86 浮点到整数 cvttsd2si 指令引发 invalid 而不是 inexact,但它跟随subsd 引发 inexact,因为两者之间没有清除异常,你可以看到它们。
  • @pmor: 顺便说一下,gcc 有一个 -fno-fp-int-builtin-inexact 选项,声称可以防止像 ceilround 这样的函数出现“不精确”异常,但它不会影响强制转换的行为.
  • @pmor:对不起,我想它实际上是(double)LLONG_MAX
【解决方案2】:

根据 C11 6.3.1.4,代码 (unsigned long long)DBL_MAX 具有未定义的行为:

当实浮点类型的有限值转换为_Bool以外的整数类型时,小数部分被丢弃(即,该值被截断为零)。如果整数部分的值不能用整数类型表示,则行为未定义

由于行为未定义,“任何事情都可能发生”,即标准未涵盖该行为。

【讨论】:

  • 也在 C17 相同的部分。
  • 当 C 标准未定义行为时,该行为可能由其他标准 (stackoverflow.com/a/65107366/1778275) 部分或全部定义。在这种情况下,它是 IEEE 754。但是,IEEE 754 说:当数字操作数将转换为目标格式范围之外的整数时,如果不能以其他方式指示这种情况,则应发出无效操作异常的信号我>。 IE。它仅提及无效操作异常,而不提及不精确异常。这个FE_INEXACT 来自哪里?为什么只针对unsigned long long
猜你喜欢
  • 1970-01-01
  • 2016-07-17
  • 1970-01-01
  • 1970-01-01
  • 2011-12-07
  • 2021-10-25
  • 1970-01-01
  • 2012-12-13
  • 1970-01-01
相关资源
最近更新 更多