如何正确避免算术运算中的SIGFPE和溢出

时间:2018-02-02 14:15:06

标签: c++ integer-overflow floating-point-exceptions sigfpe

我一直在努力创建一个尽可能完整的Fraction类,以自己学习C ++,类和相关的东西。除此之外,我想确保某种程度的保护"反对浮点异常和溢出。

目的:

避免在常见操作中发现的算术运算中出现溢出和浮点异常,从而消耗最少的时间/内存。如果无法避免,那么至少要检测它。

另外,我的想法是不要投射到更大的类型。这会产生一些问题(比如可能没有更大的类型)

我发现的案例:

  1. 溢出+, - ,*,/,pow,root

      

    操作大多是直截了当的(abLong):

         
        
    • a + b:如果LONG_MAX - b>然后有溢出。 (不够。ab可能是否定的)
    •   
    • a-b:如果LONG_MAX - a> -b然后有溢出。 (同上)
    •   
    • a * b:如果LONG_MAX / b>然后有溢出。 (如果b!= 0)
    •   
    • a / b:如果<<<<<<<<<<< b如果b<< 0
    •   
    • pow(a,b):if(pow(LONG_MAX,1.0 / b)> a然后溢出。
    •   
    • pow(a,1.0 / b):类似于a / b
    •   
  2. 当x = LONG_MIN(或等效)

    时,abs(x)溢出
      

    这很有趣。每个带符号的类型都有一个[-x-1,x]范围的可能值。 abs(-x-1)= x + 1 = -x-1因为溢出。这意味着存在abs(x)

  3. SIGFPE,大数除以-1
      

    在应用分子/ gcd(分子,分母)时找到。有时gcd返回-1,我得到一个浮点异常。

  4. 简单修复:

    1. 在某些操作上很容易检查溢出。如果是这样的话,我总是可以加倍(可能会失去对大整数的精确度)。我们的想法是找到一个更好的解决方案,而无需铸造。
        

      在Fraction算术中,有时候我可以做额外的简化检查:为了解决a / b * c / d(联合素数),我可以先减少共同素数a / d和c / b。

    2. 如果ab是< 0或>,我可以随时进行级联0.不是最漂亮的。除了糟糕的选择,我可以创建一个函数neg()来避免溢出
      T neg(T x){if (x > 0) return -x; else return x;},
      
    3. 我可以拿gcd的abs(x)和任何类似的情况(任何地方x> LONG_MIN)
    4. 我不确定2.和3.是否是最好的解决方案,但看起来还不错。我在这里发帖,所以也许有人有更好的答案。

      最丑陋的修复

      在大多数操作中,我需要做很多额外的操作来检查并避免溢出。以下是我非常确定我可以学到一两件事。

      示例:

      Fraction Fraction::operator+(Fraction f){
          double lcm = max(den,f.den);
          lcm /= gcd(den, f.den);
          lcm *= min(den,f.den);
      
          // a/c + b/d = [a*(lcm/d) + b*(lcm/c)] / lcm    //use to create normal fractions
      
          // a/c + b/d = [a/lcm * (lcm/c)] + [b/lcm * (lcm/d)]    //use to create fractions through double
      
          double p = (double)num;
          p *= lcm / (double)den;
          double q = (double)f.num;
          q *= lcm / (double)f.den;
      
          if(lcm >= LONG_MAX || (p + q) >= LONG_MAX || (p + q) <= LONG_MIN){
              //cerr << "Aproximating " << num << "/" << den << " + " << f.num << "/" << f.den << endl;
              p = (double)num / lcm;
              p *= lcm / (double)den;
              q = (double)f.num / lcm;
              q *= lcm / (double)f.den;
              return Fraction(p + q);
          }
          else
              return normal(p + q, (long)lcm);
      }  
      

      哪种方法可以避免这些算术运算出现溢出?

      编辑:这个网站中有一些问题非常相似,但是那些不一样(在特定的无关联情况下检测而不是避免,无符号而不是签名,SIGFPE)。

      检查所有这些我找到了一些答案,经过修改可能对提供答案有用,例如:

      • 检测无符号加法中的溢出(不是我的情况,我使用了签名):
      uint32_t x, y;
      uint32_t value = x + y;
      bool overflow = value < x; // Alternatively "value < y" should also work
      

      其他答案过于笼统,我想知道是否有任何答案更具体针对我所查看的案例。

1 个答案:

答案 0 :(得分:0)

您需要区分浮点操作和积分操作

关于后者,对unsigned类型的操作通常不会溢出,除了除以零,这是定义IIRC的未定义行为。这与C(++)标准强制要求无符号数的二进制表示这一事实密切相关,这实际上使它们成为一个环。

相比之下,C(++)标准允许signed个数字的多个实现(符号+幅度,1&#39; s补码或最广泛使用的2&amp;补码)。因此,签名溢出被定义为未定义的行为,可能使编译器实现者更自由地为其目标机器生成有效的代码。这也是你担心abs()的原因:至少在2的补码表示中,没有正数等于到最大负数数量级。请参阅CERT rules进行详细说明。

在浮点方SIGFPE历来被用于发信号通知浮点异常。但是,考虑到当今处理器中算术单元的各种实现,SIGFPE应被视为报告算术错误的通用信号。例如,glibc reference manual列出了可能的原因,明确包括积分除以零。

值得注意的是,我认为今天最常用的ANSI/IEEE Std 754浮点运算专门设计为一种防错。这意味着,例如,当添加溢出时,它会产生无穷大的结果,并且通常会设置一个您可以稍后检查的标记。在进一步的计算中使用这个无限值是完全合法的,因为已经为仿射算法定义了浮点运算。这曾经是为了允许长时间运行的计算(在慢速机器上)即使在中间溢出等情况下仍然继续。请注意,即使在仿射算术中也禁止某些操作,例如将无穷大除以无穷大或者用无穷大减去无穷大。

所以底线是浮点计算通常不会导致浮点异常。然而,你可以拥有所谓的陷阱,只要上面提到的标志被引发,就会触发SIGFPE(或类似的机制)。