繁体   English   中英

安全浮点分区

[英]Safe Floating Point Division

我在代码中有一些地方要确保2个任意浮点数(32位单精度)的除法不会溢出。 目标/编译器不能保证(足够明确地)对-INF / INF进行良好的处理,并且(不能完全保证IEEE 754的特殊值-(可能未定义)-目标可能会改变)。 同样,我无法在这几个特殊位置的输入上做保存假设,并且我绑定到C90标准库。

我已经阅读了每位计算机科学家应该知道的有关浮点算法的内容,但老实说,我有点迷失了。

所以...我想问社区,以下代码是否可以解决问题,以及是否有更好/更快/更精确/更正确的方法来实现:

#define SIGN_F(val) ((val >= 0.0f)? 1.0f : -1.0f)

float32_t safedivf(float32_t num, float32_t denum)
{
   const float32_t abs_denum = fabs(denum);
   if((abs_denum < 1.0f) && ((abs_denum * FLT_MAX) <= (float32_t)fabs(num))
       return SIGN_F(denum) * SIGN_F(num) * FLT_MAX;
   else
       return num / denum;
}

编辑:根据Pascal Cuoq的建议,将((abs_denum * FLT_MAX) < (float32_t)fabs(num))更改为((abs_denum * FLT_MAX) <= (float32_t)fabs(num))

您可以尝试提取num和denum的指数和尾数,并确保满足以下条件:

((exp(num) - exp (denum)) > max_exp) &&  (mantissa(num) >= mantissa(denum))

并根据输入的符号生成相应的INF。

((abs_denum * FLT_MAX) < (float32_t)fabs(num) ,乘积abs_denum * FLT_MAX可能会舍入并最终等于fabs(num) ,这并不意味着num / denum在数学上不大于FLT_MAX ,并且你应该担心它可能发生导致要避免溢出。你最好更换此<通过<=


对于替代解决方案,如果可以使用double类型并且比float宽,则计算(double)num/(double)denum可能更经济。 如果float是binary32ish而double是binary64ish,则double除法溢出的唯一方法是denum为(a)为零(如果denum为零,则您的代码也有问题)。

double dbl_res = (double)num/(double)denum;
float res = dbl_res < -FLT_MAX ? -FLT_MAX : dbl_res > FLT_MAX ? FLT_MAX : (float)dbl_res;

当商接近FLT_MAXnum, denom请小心使用num, denom FLT_MAX

以下使用受OP启发的测试,但与FLT_MAX附近的结果保持距离。 正如@Pascal Cuoq指出的那样,舍入可能只会将结果推到边缘。 相反,它使用FLT_MAX/FLT_RADIX FLT_MAX*FLT_RADIXFLT_MAX*FLT_RADIX FLT_MAX/FLT_RADIX阈值。

通过使用FLT_RADIX (通常为2)进行缩放,代码应始终获得准确的结果。 在任何舍入模式下舍入均不会影响结果。

就速度而言,“快乐之路”,即当结果肯定不会溢出时,应该是快速的计算。 仍然需要进行单元测试,但是注释应提供这种方法的要点。

static int SD_Sign(float x) {
  if (x > 0.0f)
    return 1;
  if (x < 0.0f)
    return -1;
  if (atan2f(x, -1.0f) > 0.0f)
    return 1;
  return -1;
}

static float SD_Overflow(float num, float denom) {
  return SD_Sign(num) * SD_Sign(denom) * FLT_MAX;
}

float safedivf(float num, float denom) {
  float abs_denom = fabsf(denom);
  // If |quotient| > |num|
  if (abs_denom < 1.0f) {
    float abs_num = fabsf(num);
    // If |num/denom| > FLT_MAX/2 --> quotient is very large or overflows
    // This computation is safe from rounding and overflow.
    if (abs_num > FLT_MAX / FLT_RADIX * abs_denom) {
      // If |num/denom| >= FLT_MAX*2 --> overflow
      // This also catches denom == 0.0
      if (abs_num / FLT_RADIX >= FLT_MAX * abs_denom) {
        return SD_Overflow(num, denom);
      }
      // At this point, quotient must be in or near range FLT_MAX/2 to FLT_MAX*2
      // Scale parameters so quotient is a FLT_RADIX * FLT_RADIX factor smaller.
      if (abs_num > 1.0) {
        abs_num /= FLT_RADIX * FLT_RADIX;
      } else {
        abs_denom *= FLT_RADIX * FLT_RADIX;
      }
      float quotient = abs_num / abs_denom;
      if (quotient > FLT_MAX / (FLT_RADIX * FLT_RADIX)) {
        return SD_Overflow(num, denom);
      }
    }
  }
  return num / denom;
}

SIGN_F()需要考虑denum+0.0-0.0 @Pascal Cuoq在评论中提到的各种方法:

  1. copysign()signbit()
  2. 使用工会

另外,某些函数在正确实现时会区分+/-零,例如atan2f(zero, -1.0)sprintf(buffer, "%+f", zero)

注意:为简单起见,使用floatfloat32_t
注意:也许使用fabsf()而不是fabs()
次要:建议用denom (分母)代替denum

为了避免出现四舍五入的情况,可以用frexp()和ldexp()按摩除数上的指数,并担心结果是否可以缩小而不会溢出。 或者使用frexp()这两个参数,并手动完成指数工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM