为什么转换(无符号长整长)DBL_MAX(或FLT_MAX)也会引起FE_INEXACT的升高? [英] Why conversion (unsigned long long)DBL_MAX (or FLT_MAX) causes raising of FE_INEXACT as well?

查看:65
本文介绍了为什么转换(无符号长整长)DBL_MAX(或FLT_MAX)也会引起FE_INEXACT的升高?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

代码(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 升高?

Question: why conversion (unsigned long long)DBL_MAX (or FLT_MAX) causes raising of FE_INEXACT as well?

推荐答案

我想您正在x86上对此进行测试,因为这是我所看到的行为所在.示例.这是底层的解释.

I suppose you're testing this on x86, since that's where I see the behavior you describe. Example. Here's the low-level explanation.

在x86-64上,至少gcc使用 cvttsd2si进行大多数浮点数到整数的转换指令,该指令将双精度浮点数转换为32位或64位 signed 整数,从而产生无效"的整数.如果结果超出范围,则为异常.该指令可用于转换为任何有符号整数类型,也可转换为32位或更低位的无符号整数类型-例如,可以通过转换为有符号64位并丢弃高位来完成对无符号32位的转换.

On x86-64, gcc, at least, does most floating-point to integer conversion with the cvttsd2si instruction, which converts a double-precision floating point number to a 32- or 64-bit signed integer, raising an "invalid" exception if the result is out of range. This instruction can be used to convert to any signed integer type, and also to unsigned integer types of 32 bits or lower - for instance, a conversion to unsigned 32-bit can be done by converting to signed 64-bit and discarding high bits.

但这不适用于转换为无符号的64位,因为输入的数字可能不适合带符号的64位,但适合无符号的64位,并且x86没有指令使之直接转换.这样,需要一些额外的算术,并且正是这些额外的指令才产生"inexact".例外.(具体来说,它会执行 subsd 从输入中减去(double)LLONG_MAX ,当输入为 DBL_MAX .)

But this does not work for conversion to unsigned 64-bit, since the input might be a number that doesn't fit in signed 64-bit but would fit in unsigned 64-bit, and x86 has no instruction to make that conversion directly. As such, some extra arithmetic is needed, and it's these additional instructions that produce the "inexact" exception. (Specifically, it does a subsd to subtract (double)LLONG_MAX from the input, which does indeed result in a loss of precision when the input is DBL_MAX.)

请参见未签名的64位要加倍转换:为什么使用这种来自g ++ 的算法来举例说明gcc尽可能有效地做到这一点的体操形式.

See Unsigned 64-bit to double conversion: why this algorithm from g++ for an example of the sorts of gymnastics that gcc does to do this as efficiently as possible.

请注意,在x86-64上,您实际上还可以看到 FP_INEXACT 转换为 unsigned long 的情况,因为它与 unsigned long long 相同.我得到了您在x86-32上观察到的确切行为,其中 unsigned long long 是唯一适用于此的64位类型.在这种情况下,代码要复杂一些,如果您真的感兴趣的话,我会把它留给您通读汇编.

Note that on x86-64 you actually see FP_INEXACT with conversion to unsigned long as well, since it's the same as unsigned long long. I get the exact behavior you observe on x86-32, where unsigned long long is the only 64-bit type to which this applies. The code in that case is a bit more complicated and I would leave it to you to read through the assembly if you are really interested.

相反,当我在AArch64上运行此代码时,所有行仅给出 FE_INVALID .那是因为AArch64确实有专用指令将浮点转换为无符号64位( fcvtzu ),因此没有进一步的算法可能涉及不精确的结果.

By contrast, when I run this code on AArch64, all lines simply give FE_INVALID. That's because AArch64 does have a dedicated instruction to convert floating point to unsigned 64-bit (fcvtzu) and so there's no further arithmetic that could involve an inexact result.

这篇关于为什么转换(无符号长整长)DBL_MAX(或FLT_MAX)也会引起FE_INEXACT的升高?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆