首页 文章

IEEE-754:“最小”溢出条件

提问于
浏览
2

Before I start, just some background information:

我正在使用编译器标准数学库(符合IEEE-754)在Keil uVision3中编译的ARM7微控制器(LPC2294 / 01)上运行裸机应用程序 .

The issue: I 'm having trouble wrapping my head around what exactly constitutes an '溢出'对2个单精度浮点输入的总和 .

最初,我的印象是,如果我试图将任何正值添加到可以用IEEE-754表示法表示的最大值,结果将产生溢出异常 .

例如,假设我有:

a = 0x7f7fffff (ie. 3.4028235..E38);
 b = 0x3f800000 (ie. 1.0)

我期望将这两个值相加会导致IEEE-754中定义的溢出 . 令我最初的惊讶,结果只返回'a'的值,没有标记异常 .

所以我想,因为精确度(或者你喜欢的分辨率)随着所表示的值的增加而减小,所以在这种情况下,值“1”可能由于其相对不重要而有效地向下舍入到0 .

So that begged the question: 在这种情况下,'b'的最小值会导致溢出异常吗?它取决于IEEE-754的具体实现吗?

也许就像我不了解如何在这种特殊情况下确定最小“重要”精度一样简单,但是考虑到下面的代码,为什么第二个和会导致溢出而不是第一个?

static union sFloatConversion32
{
     unsigned int unsigned32Value;
     float floatValue;
} sFloatConversion32;

t_bool test_Float32_Addition(void)
{
   float a;
   float b;
   float c;

   sFloatConversion32.unsigned32Value = 0x7f7fffff;
   a = sFloatConversion32.floatValue;

   sFloatConversion32.unsigned32Value = 0x72ffffff;
   b = sFloatConversion32.floatValue;

   /* This sum returns (c = a) without overflow */
   c = a + b;

   sFloatConversion32.unsigned32Value = 0x73000000;
   b = sFloatConversion32.floatValue;

   /* This sum, however, causes an overflow exception */
   c = a + b;
}

是否存在可以应用的通用规则,使得可以提前知道(即,不执行总和),给定两个浮点数,它们的总和将导致IEEE-754定义的溢出?

3 回答

  • 1

    当结果受格式范围影响时,会发生溢出 . 只要正常舍入将结果保持在有限范围内,就不会发生溢出,因为结果与指数无界时的结果相同 - 在考虑范围之前,结果通过正常舍入减少 . 所以由于范围没有例外 .

    当舍入结果不适合格式的有限范围时,则无法生成有限结果,因此会发生溢出异常并产生无穷大 .

    在IEEE 754中,正常操作实际上是两个步骤:

    • 计算确切的数学结果 .

    • 将精确的数学结果舍入到最接近的可表示值 .

    当且仅当上述结果的大小超过最大可表示的有限值时,IEEE 754定义溢出 . 换句话说,溢出不会仅因为你超过最大可表示值而发生,但只有当你远远超过最大可表示值时,算术在浮点运行的常规方法才起作用 .

    因此,如果从最大的可表示值开始并向其添加一个小数字,结果将简单地舍入到最大可表示值(当使用舍入到最近时) . IEEE 754认为这是正常的 - 所有算术运算都是圆的,并且如果该舍入使结果保持在边界内,则这是正常且无法实现的 . 即使指数范围是无界的,正常的舍入也会产生相同的结果 . 由于这是不受有限范围影响的正常结果,因此没有发生任何异常情况 .

    只有当数学结果如此之大以至于如果我们不受指数限制,舍入将产生下一个更高的数字时,才会发生溢出 . (但是,既然我们已达到指数范围的极限,我们必须返回无穷大 . )

    IEEE-754基本32位二进制浮点中可表示的最大值为2128-2104 . 此时,可表示数字之间的步长以2104为单位 . 使用舍入到最接近的规则,将少于半步的任何数字2103加到此处将舍入到2128-2104,并且不会发生溢出 . 如果你添加一个大于2103的数字,那么如果指数可以达到那么高,结果将舍入到2128 . 相反,会产生无穷大并发生溢出异常 . (如果你精确地添加了2103,则使用关系规则 . 这个规则说选择具有偶数低位的候选者 . 这会产生2128,所以它也会溢出 . )

    因此,对于圆到最近,溢出发生在步的中点 . 使用其他舍入规则,溢出发生在不同的点 . 使用round-towards-infinity(向上舍入),将任何正值(即2-149)添加到2128-2104将导致溢出 . 舍入为零时,添加任何小于2104到2128-2104的值都不会溢出 .

  • 1

    这取决于IEEE-754的具体实施吗?

    是,并且当时的舍入模式处于活动状态 .

    考虑 x before maxFLT_MAX 之间的步骤 .

    float max = FLT_MAX;
    float before_max = nextafterf(max, 0.0f);
    float delta = max - before_max;
    printf("max:   %- 20a %.*g\n", max, FLT_DECIMAL_DIG, max);
    printf("1st d: % -20a %.*g\n", delta, FLT_DECIMAL_DIG, delta);
    // Typical output
    max:    0x1.fffffep+127     3.40282347e+38
    b4max:  0x1.fffffep+127     3.40282347e+38
    1st d:  0x1p+104            2.02824096e+31
    

    最大的 float 大约是 float 的两倍,同样的最小 float 具有相同的步骤或ULP . 可以想象这个较小的 float ,其所有显式精度位清零,而设置为 FLOAT_MAX .

    float m0 = nextafterf(max/2, max);
    printf("m0:    %- 20a %.*g\n", m0, FLT_DECIMAL_DIG, m0);
    // m0:     0x1p+127            1.70141183e+38
    

    现在将其与 FLT_EPSILON 进行比较,这是从1.0到下一个更大的 float 的最小步骤:

    float eps = FLT_EPSILON;
    printf("epsil: %- 20a %.*g\n", eps, FLT_DECIMAL_DIG, eps);
    // Output
    // epsil:  0x1p-23             1.1920929e-07
    

    请注意比率 delta/m0FLT_EPSILON .

    float r = delta1/m0;
    printf("r:     %- 20a %.*g\n", r, FLT_DECIMAL_DIG, r);
    // r:      0x1p-23             1.1920929e-07
    

    考虑舍入到最近的典型舍入模式,与偶数的关系 .
    现在让我们尝试将 1/2*delta1 添加到 FLOAT_MAX ,然后尝试添加下一个较小的 float .

    sum = max + delta1/2;
    printf("sum:        % -20a %.*g\n", sum, FLT_DECIMAL_DIG, sum);
    sum = nextafterf(sum, 0);
    printf("sum:        % -20a %.*g\n", sum, FLT_DECIMAL_DIG, sum);
    // sum:         inf                 inf
    // sum:         0x1.fffffep+127     3.40282347e+38
    

    IEEE-754:“最小”溢出条件

    如果约 FLT_MAX*1/2*1/2*FLOAT_EPSILON ,我们可以看到最小的delta .

    float small = FLT_MAX*0.25f*FLT_EPSILON;
    printf("small: %- 20a %.*g\n", small, FLT_DECIMAL_DIG, small);
    printf("sum:        % -20a %.*g\n", max+small, FLT_DECIMAL_DIG, max+small);
    small = nextafterf(small, max);
    printf("sum:        % -20a %.*g\n", max+small, FLT_DECIMAL_DIG, max+small);
    // sum:         0x1.fffffep+127     3.40282347e+38
    // sum:         inf                 inf
    

    鉴于 float 的各种可能编码,您的结果可能会有所不同,但这种方法可以确定如何确定导致溢出的最小增量 .

  • -1

    运行这个程序足够长的时间,看看会发生什么:

    float x = 10000000.0f;
    while(1)
    {
        printf("%f\n", x);
        x += 1.0f;
    }
    

    我想它会回答你的问题 .

相关问题