当结果不是正常时,浮点状态标志FE_UNDERFLOW可以设置吗?

chu*_*ica 6 c floating-point ieee-754

在调查浮点异常状态标志时,我遇到了一个奇怪的情况,即FE_UNDERFLOW在不期望的情况下设置状态标志.

这类似于何时发生下溢?然而进入可能是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!?
Run Code Online (Sandbox Code Playgroud)

我没想到FE_UNDERFLOW设置z如上是正常的,不是正常的.
我希望FE_UNDERFLOW结果较早浮点运算的低于正常与精度的损失.在这种情况下,精度会下降.

float和我一起尝试了这个long double并且得到了相同的结果.

经过大量调查后,我注意到,__STDC_IEC_559__没有确定.

问题

  1. 如果__STDC_IEC_559__定义了,在这种情况下,下溢的正确状态是什么?

  2. 由于缺乏定义,__STDC_IEC_559__我坚持使用" 未定义的实现__STDC_IEC_559__不需要符合这些规范".C11或是否有一些C规范表明此结果不正确?

  3. 由于这肯定是我的硬件(处理器)的结果,您的结果可能会有所不同,这将是有趣的.


以下是一些测试代码,用于演示此功能.起初我怀疑是因为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');
}
Run Code Online (Sandbox Code Playgroud)

司机

// 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");
}
Run Code Online (Sandbox Code Playgroud)

产量

__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
Run Code Online (Sandbox Code Playgroud)

参考

IEEE_754

实施说明:

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');
//                    ^^^^^
Run Code Online (Sandbox Code Playgroud)

chu*_*ica 4

虽然无意作为自我回答,但来自不同评论者@John Bollinger@nwellnhof的输入和进一步的研究导致:

\n
\n

当结果不低于正常值时,浮点状态标志 FE_UNDERFLOW 是否可以设置?

\n
\n

是的,在狭窄的情况下 - 当数学答案介于次正常值和正常值之间时。参见下文。

\n
\n

“下溢”发生在以下情况:

\n
\n

如果数学结果的量级太小,以至于无法在指定类型的对象中表示数学结果,而没有异常舍入误差,则结果下溢。C11 7.12.1 错误情况的处理

\n
\n

以上z = y/2;是 1) 不精确(由于四舍五入)和 2) 可能被认为“太小”。

\n
\n

数学

\n

可以z = y/2;认为要经历两个阶段:划分和舍入。具有无限精度的数学商小于最小正规数FLT_MIN且大于最大次正规数 nextafterf(FLT_MIN,0)。根据舍入模式,最终答案是这两者之一。与FE_TONEAREST,z一起分配FLT_MIN, 一个正常的数字。

\n

规格

\n

下面的 C 规范和 IEC 60559 表明

\n
\n

每当结果很小(基本上低于正常或零)并遭受精度损失时,就会引发“下溢”浮点异常。358 C11 \xc2\xa7F.10 7.
\n 358 IEC 60559 允许不同的下溢定义。它们都产生相同的值,但在引发浮点异常时有所不同。

\n
\n

\n
\n

允许两个定义来确定“微小”条件:在将无限精确结果舍入到工作精度之前或之后,具有无界指数。

\n

754r 的附录 U 建议,只有舍入后的微小和不精确(如精度损失)才是下溢信号的原因。 维基参考

\n
\n

(我的重点)

\n
\n

问答

\n
\n
    \n
  1. 如果定义了STDC_IEC_559,那么在这种情况下下溢的正确状态是什么?
  2. \n
\n
\n

在这种情况下,可以设置下溢标志或将其保留。要么遵守。不过,有一个偏好是不设置下溢标志。

\n
\n

2 由于缺乏定义的STDC_IEC_559,我坚持“未定义STDC_IEC_559的实现不需要符合这些规范”。C11 或者是否有一些 C 规范表明此结果不正确?

\n
\n

下溢标志的设置不会导致错误。FP 规范允许这种行为。它还允许不设置下溢标志。

\n
\n

3 由于这肯定是我的硬件(处理器)的结果,因此您的结果可能会有所不同,了解这一点很有趣。

\n
\n

在另一个平台上,__STDC_IEC_559__ = not defineFLT_EVAL_METHOD = 0FE_INEXACT FE_UNDERFLOW都设置了标志,就像上面的 tst 情况一样。该问题适用于float, double, long double.

\n
\n

如果数学答案位于下面的灰色“之间”区域,它将根据其值和舍入模式向下舍入到次正常值double或向上舍入到正常值。double DBL_MIN如果向下舍入,则FE_UNDERFLOW肯定已设置。如果向上舍入,则FE_UNDERFLOW可以根据何时应用“微小”条件的确定来设置或不设置。

\n

DBL_MIN 附近的数轴

\n