c - 为什么转换(无符号长长)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
?
解决方案
我想您正在 x86 上对此进行测试,因为那是我看到您描述的行为的地方。 例子。这是低级解释。
在 x86-64 上,gcc 至少使用cvttsd2si指令进行大多数浮点到整数的转换,该指令将双精度浮点数转换为 32 位或 64 位有符号整数,如果出现“无效”异常结果超出范围。该指令可用于转换为任何有符号整数类型,也可用于转换为 32 位或更低的无符号整数类型 - 例如,可以通过转换为有符号 64 位并丢弃高位来转换为无符号 32 位。
但这不适用于转换为无符号 64 位,因为输入可能是一个不适合有符号 64 位但适合无符号 64 位的数字,并且 x86 没有直接进行该转换的指令。因此,需要一些额外的算术,正是这些额外的指令产生了“不精确”的异常。(具体来说,它会从输入subsd
中减去(double)LLONG_MAX
,当输入为 时,这确实会导致精度损失DBL_MAX
。)
请参阅无符号 64 位到双精度转换:为什么 g++中的这种算法,以了解 gcc 为尽可能高效地执行此操作而采取的各种体操的示例。
请注意,在 x86-64 上,您实际上也可以看到FP_INEXACT
转换unsigned long
为,因为它与unsigned long long
. 我得到了您在 x86-32 上观察到的确切行为,这unsigned long long
是唯一适用的 64 位类型。这种情况下的代码有点复杂,如果您真的感兴趣,我会留给您通读程序集。
相比之下,当我在 AArch64 上运行此代码时,所有行都简单地给出FE_INVALID
. 这是因为 AArch64 确实有一条专用指令将浮点数转换为无符号 64 位 ( fcvtzu
),因此没有进一步的算术可能涉及不精确的结果。
推荐阅读
- node.js - 创建一个新的 React 应用程序需要几个小时而没有成功
- dynamic - OpenMp:如何确保每个线程在动态调度中至少工作 1 次迭代
- angular - Angular:如何在文件角度应用程序上获得更小的尺寸?
- mysql - 在 R 中使用 SQLite 包选择不同的
- javascript - 带水印和调整大小的php图像上传
- selenium - winapp驱动中的动态等待
- javascript - 如何使用 Javascript 在泰米尔语 unicode 单词中获得正确的第一个字母?
- mysql - Why edit contents of docker-compose.yml are not reflected?
- git - 从 git 存储库中永久排除文件
- php - 我正在开发一个 laravel 网站,当我部署在服务器上时,我得到了这个数据库错误