在调查浮点异常状态标志时,我遇到了一个奇怪的情况,即在不期望的情况下设置状态标志FE_UNDERFLOW
。
这与When does underflow occur?类似,但却涉及可能是C规范问题或FP硬件缺陷的极端情况。
// pseudo code
// s bias_expo implied "mantissa"
w = smallest_normal; // 0 000...001 (1) 000...000
x = w * 2; // 0 000...010 (1) 000...000
y = next_smaller(x); // 0 000...001 (1) 111...111
round_mode(FE_TONEAREST);
clear_status_flags();
z = y/2; // 0 000...001 (1) 000...000
FE_UNDERFLOW is set!?
我没想到FE_UNDERFLOW
被设置为上面的z
是正常的,而不是正常的。
当早期浮点运算的结果为低于正常且精度不足时,我期待FE_UNDERFLOW
。在这种情况下,精度会下降。
我在我的float
和long double
上尝试了此操作,结果相同。
经过大量调查后,我注意到__STDC_IEC_559__
未已定义。
问题
如果定义了__STDC_IEC_559__
,在这种情况下,下溢的正确状态是什么?
由于缺少定义__STDC_IEC_559__
,我坚持认为“不定义__STDC_IEC_559__
的实现不需要符合这些
规范。“C11或是否有一些C规范表明这个结果不正确?
由于这肯定是我的硬件(处理器)的结果,您的结果可能会有所不同,这一点很有趣。
以下是一些测试代码,用于演示此功能。起初我怀疑这是因为FLT_EVAL_METHOD = 2
在我的机器上,然后我尝试使用long double
类似的代码并获得相同的结果。
// These 2 includes missing in original post, yet in my true test code
#include <float.h>
#include <math.h>
#include <fenv.h>
#include <stdio.h>
#include <stdint.h>
#define N (sizeof excepts/sizeof excepts[0])
void Report_IEC_FP_exception_status_flags(const char *s) {
printf("%s", s);
int excepts[] = { //
FE_DIVBYZERO, FE_INEXACT, FE_INVALID, FE_OVERFLOW, FE_UNDERFLOW, };
const char *excepts_str[N] = { //
"FE_DIVBYZERO", "FE_INEXACT", "FE_INVALID", "FE_OVERFLOW", "FE_UNDERFLOW", };
int excepts_val[N];
for (unsigned i = 0; i < N; i++) {
excepts_val[i] = fetestexcept(excepts[i]);
}
for (unsigned i = 0; i < N; i++) {
if (excepts_val[i]) printf(" %s", excepts_str[i]);
}
printf("\n");
fflush(stdout);
}
#undef N
void test2(float f, int round_mode, const char *name) {
union {
float f;
uint32_t u32;
} x = { .f = f};
printf("x:%+17a %08lX normal:%c round_mode:%d %s\n", //
f, (unsigned long) x.u32, isnormal(f) ? 'Y' : 'n', round_mode, name);
if (feclearexcept(FE_ALL_EXCEPT)) puts("Clear Fail");
Report_IEC_FP_exception_status_flags("Before:");
f /= 2;
Report_IEC_FP_exception_status_flags("After :");
printf("y:%+17a %08lX normal:%c\n\n",
f,(unsigned long) x.u32, isnormal(f) ? 'Y' : 'n');
}
驱动程序
// In same file as above
int main(void) {
#ifdef __STDC_IEC_559__
printf("__STDC_IEC_559__ = %d\n", __STDC_IEC_559__);
#else
printf("__STDC_IEC_559__ = not define\n");
#endif
float f = FLT_MIN;
printf("FLT_EVAL_METHOD = %d\n", FLT_EVAL_METHOD);
printf("FLT_MIN:%+17a\n", f);
f *= 2.0f;
test2(f, FE_TONEAREST, "FE_TONEAREST");
f = nextafterf(f, 0);
test2(f, FE_TONEAREST, "FE_TONEAREST"); // *** problem? ***
f = nextafterf(f, 0);
test2(f, FE_TONEAREST, "FE_TONEAREST");
}
输出
__STDC_IEC_559__ = not define
FLT_EVAL_METHOD = 2
FLT_MIN: +0x1p-126
x: +0x1p-125 01000000 normal:Y round_mode:0 FE_TONEAREST
Before:
After :
y: +0x1p-126 01000000 normal:Y
x: +0x1.fffffep-126 00FFFFFF normal:Y round_mode:0 FE_TONEAREST
Before:
After : FE_INEXACT FE_UNDERFLOW *** Why FE_UNDERFLOW? ***
y: +0x1p-126 00FFFFFF normal:Y *** Result is normal ***
x: +0x1.fffffcp-126 00FFFFFE normal:Y round_mode:0 FE_TONEAREST
Before:
After :
y: +0x1.fffffcp-127 00FFFFFE normal:n
参考
实施说明:
GNU C11(GCC)版本6.4.0(i686-pc-cygwin) 由GNU C版本6.4.0,GMP版本6.1.2,MPFR版本3.1.5-p10,MPC版本1.0.3编译,isl版本0.14或0.13
glibc 2.26发布。
Intel Xeon W3530,64位操作系统(Windows 7)
[次要更新]商的说明性打印为32位十六进制数应使用y.u32
。这不会改变测试中的功能
// printf("y:%+17a %08lX normal:%c\n\n",
// f,(unsigned long) x.u32, isnormal(f) ? 'Y' : 'n');
union {
float f;
uint32_t u32;
} y = { .f = f};
printf("y:%+17a %08lX normal:%c\n\n",
f,(unsigned long) y.u32, isnormal(f) ? 'Y' : 'n');
// ^^^^^
答案 0 :(得分:2)
虽然不是self answer,但是来自各个评论者@John Bollinger,@nwellnhof和进一步研究的意见导致:
在狭隘的情况下,当结果不是正常时,是否可以设置浮点状态标志FE_UNDERFLOW?
是。见下文。
“下溢”发生在:
如果数学结果的大小非常小,以至于在指定类型的对象中无法表示数学结果而没有非常的舍入误差,则结果会下溢。 C11 7.12.1错误条件的处理
上面的z = y/2;
是1)不精确(由于四舍五入)和2)可能被认为“太小”。
<强>数学强>
z = y/2;
可以被认为经历了两个阶段:分裂和舍入。具有无限精度的数学商小于最小正常数FLT_MIN
并且大于最大sub-normal number nextafterf(FLT_MIN,0)
。根据舍入模式,最终答案是这两者之一。使用FE_TONEAREST
,z
被指定为FLT_MIN
,这是正常的数字。
<强>规格强>
以下C规范和IEC 60559表示
只要结果很小(基本上是次正常或零),就会引发“下溢”浮点异常,并且会失去准确性。 358 C11§F.107。
358 IEC 60559允许不同的下溢定义。它们都会产生相同的值,但在引发浮点异常时会有所不同。
和
允许两个定义用于确定“微小”条件:在舍入之前或之后无限精确结果到工作精度,具有无界指数。
754r的附件U建议,只有在舍入后才会出现细微之处并且由于精度损失而导致不精确是造成下溢信号的原因。 wiki reference
(我的重点)
Q&amp; A 强>
- 如果定义 STDC_IEC_559 ,在这种情况下,下溢的正确状态是什么?
醇>
在这种情况下,可以设置或保留下溢标志。要么符合要求。但是,有一个偏好,因为没有设置下溢标志。
2由于缺少定义的 STDC_IEC_559 ,我坚持认为“未定义 STDC_IEC_559 的实现不需要符合这些规范。” C11或是否有一些C规范表明此结果不正确?
下溢标志的设置导致不正确。 FP规范允许这种行为。它还允许不设置下溢标志。
3由于这肯定是我的硬件(处理器)的结果,您的结果可能会有所不同,这一点很有趣。
在__STDC_IEC_559__ = not define
和FLT_EVAL_METHOD = 0
的另一个平台上,FE_INEXACT FE_UNDERFLOW
标志都已设置,就像上面的tst情况一样。该问题适用于float, double, long double
。
如果数学答案位于下方灰色的“之间”区域,它将向下舍入到低于正常double
或高达正常double
DBL_MIN
,具体取决于其值和舍入模式。如果向下舍入,则肯定会设置FE_UNDERFLOW
。如果向上舍入,则可以设置FE_UNDERFLOW
,具体取决于何时应用'微小'条件的确定。