C 安全浮点除法

C 安全浮点除法,c,floating-point,division,divide-by-zero,c89,C,Floating Point,Division,Divide By Zero,C89,在我的代码中有一些地方我想确保2个任意浮点数(32位单精度)的除法不会溢出。目标/编译器不能保证(足够明确地)很好地处理-INF/INF,也不能完全保证IEEE 754中的异常值(可能未定义),目标可能会更改。此外,我无法对这几个特殊位置的输入进行保存假设,我必须使用C90标准库 我读过,但老实说,我有点迷路了 所以。。。我想问一下社区,下面的代码是否能起到作用,是否有更好/更快/更精确/更正确的方法: #define SIGN_F(val) ((val >= 0.0f)? 1.0f :

在我的代码中有一些地方我想确保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;
}
#定义符号F(val)((val>=0.0f)?1.0f:-1.0f)
float32_t safedivf(float32_t num,float32_t denum)
{
常量浮点数32_t abs_denum=fabs(denum);

如果((abs_denum<1.0f)&((abs_denum*FLT_MAX)您可以尝试提取num和denum的指数和尾数,并确保条件:

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

并且根据输入的符号,在
((abs_denum*FLT_MAX)<(float32_t)fabs(num)
中生成相应的INF.

,产品
abs_denum*FLT_MAX
可能向下取整,最终等于
fabs(num)
。这并不意味着
num/denum
在数学上不大于
FLT\u MAX
,您应该担心它可能会导致您想要避免的溢出。当商接近
FLT\u MAX
时,您最好将此
替换为
num,denom

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

通过使用
FLT_基数
(通常为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;
}
静态整数符号(浮点x){
如果(x>0.0f)
返回1;
如果(x<0.0f)
返回-1;
如果(atan2f(x,-1.0f)>0.0f)
返回1;
返回-1;
}
静态浮点SD_溢出(浮点数、浮点数){
返回标准符号(num)*标准符号(denom)*FLT_MAX;
}
float safedivf(float num,float denom){
浮动abs_denom=fabsf(denom);
//如果|商|>数|
如果(绝对值<1.0f){
浮动abs_num=fabsf(num);
//如果| num/denom |>FLT_MAX/2-->商非常大或溢出
//此计算不会出现舍入和溢出。
如果(abs_num>FLT_MAX/FLT_RADIX*abs_denom){
//如果| num/denom |>=FLT|u MAX*2-->溢出
//这也捕获了denom==0.0
如果(abs\u num/FLT\u基数>=FLT\u MAX*abs\u denom){
返回SD_溢出(num,denom);
}
//此时,商必须在FLT_MAX/2至FLT_MAX*2范围内或附近
//缩放参数,使商是FLT_基数*FLT_基数因子更小。
如果(绝对值>1.0){
abs_num/=FLT_基数*FLT_基数;
}否则{
abs_denom*=FLT_基数*FLT_基数;
}
浮动商=绝对值/绝对值;
if(商>FLT_MAX/(FLT_基数*FLT_基数)){
返回SD_溢出(num,denom);
}
}
}
返回num/denom;
}
<代码>符号>(或)代码>需要考虑在<代码> DuNU< <代码>是<代码> + 0 < /代码>或<代码> -0 < /代码> .评论中的@ Pascal Cuoq提到的各种方法:

  • copysign()
    signbit()
  • 使用工会
  • 另外,一些函数在得到很好的实现后,会在+/-零上进行区分,比如
    atan2f(zero,-1.0)
    sprintf(buffer,“%+f”,zero)

    注意:为了简单起见,使用了
    float
    vs.
    float32\u t

    注意:可能使用
    fabsf()
    而不是
    fabs()


    小调:建议使用
    denom
    (分母)代替
    denum

    为了避免四舍五入的拐角情况,您可以使用frexp()和ldexp()在除数上按摩指数,并担心结果是否可以在不溢出的情况下缩小。或frexp()这两个论点,并手工完成演示工作。

    Goldberg的论文在第一次阅读时就失去了大多数人,对于初学者来说,这是一个可怕的资源,学习如何使用f-p数字和算术。它针对的是那些为实现f-p数字和算法而设计软件和硬件的人。你最好从一开始请求原谅比允许要好
    SIGN\u F(val)
    无法正确提取
    -0.0
    的符号,因此
    1.0/-0.0
    最终将成为
    +FLT\u MAX
    。您可能会介意,也可能不会介意。如果您不确定IEEE 754,可能也无法确定
    copysign
    ,但如果它存在,您也可以使用它。@MarkA.是的,对于
    =
    ,等等,带符号的零是不可区分的。您必须1-使用
    copysign
    ,或2-通过
    联合
    访问表示,或3-除以它并导致您试图避免的溢出。这就剩下了解决方案2-。尝试执行除法。如果失败,则检测该失败并作出相应反应。Tryi提前预测失败与继续进行除法一样困难,事实上可能更困难。请指出,我的(更正)在哪里/如果在0.0的情况下,除了符号问题之外,代码也是有问题的?@MarkA。我只是指符号问题。Downvoter,有什么解释为什么你觉得这个答案特别糟糕吗?我也很惊讶,因为这个解决方案-加上一些-0.0符号检测器
    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;
    }