安全浮点分部

时间:2014-08-14 14:03:20

标签: c floating-point division divide-by-zero c89

我的代码中有一些地方,我想确保2个任意浮点数(32位单精度)的除法不会溢出。目标/编译器不保证(显然足够)良好地处理-INF / INF和(不完全保证IEEE 754的异常值 - (可能未定义) - 并且目标可能会改变)。此外,我无法对这几个特殊地点的输入进行保存,我必须使用C90标准库。

我已阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic但说实话,我有点失落。

所以......我想问一下社区,如果下面的代码可以解决这个问题,并且有更好/更快/更高/更正/更正的方法:

#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))

4 个答案:

答案 0 :(得分:3)

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

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

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

答案 1 :(得分:1)

((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;

答案 2 :(得分:1)

当商在num, denom附近时,请小心使用FLT_MAX

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

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

就速度而言,快乐的路径&#34;,即当结果肯定不会溢出时,应该是一个快速的计算。仍然需要进行单元测试,但评论应该提供这种方法的要点。

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. 使用联盟
  3. 另外,一些功能在实施良好时,可以区分+/- 0,如atan2f(zero, -1.0)sprintf(buffer, "%+f", zero)

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

答案 3 :(得分:0)

为了避免带有舍入的拐角情况以及什么不可以,你可以按下除数上的指数 - 使用frexp()和ldexp() - 并担心结果是否可以缩小而不会溢出。或者frexp()这两个参数,并且手工完成exponenent工作。