我一直在尝试创建一个尽可能完整的 Fraction 类,以自学 C++、类和相关的东西。除其他外,我想确保某种程度的“保护”免受浮点异常和溢出。
客观的:
避免常见运算中出现的算术运算中的溢出和浮点异常,消耗最少的时间/内存。如果避免是不可能的,那么至少检测它。
此外,这个想法是不要转换为更大的类型。这会产生一些问题(比如可能没有更大的类型)
我发现的案例:
+、-、*、/、pow、root 上的溢出
操作大多很简单(
a
并且b
是Long
):- a+b:如果 LONG_MAX - b > a 则存在溢出。(还不够。
a
或者b
可能是负面的) - ab:如果 LONG_MAX - a > -b 则存在溢出。(同上)
- a*b:如果 LONG_MAX / b > a 则存在溢出。(如果 b != 0)
- a/b:如果 a << b 可能会抛出 SIGFPE,如果 b << 0 则可能会溢出
- pow(a,b): if (pow(LONG_MAX, 1.0/b) > a 那么有溢出。
- pow(a,1.0/b):类似于 a/b
- a+b:如果 LONG_MAX - b > a 则存在溢出。(还不够。
当 x = LONG_MIN(或等效项)时 abs(x) 溢出
这很好笑。每个有符号类型都有一个范围 [-x-1,x] 的可能值。abs(-x-1) = x+1 = -x-1 因为溢出。这意味着存在 abs(x) < 0 的情况
- 具有大数字的 SIGFPE 除以 -1
应用分子/gcd(分子,分母)时发现。有时 gcd 返回 -1,我得到一个浮点异常。
简单的修复:
- 在某些操作上很容易检查溢出。如果是这种情况,我总是可以转换为 double (有失去大整数精度的风险)。这个想法是找到一个更好的解决方案,而不是强制转换。
在分数算术中,有时我可以进行额外的简化检查:为了解决 a/b * c/d(共素数),我可以先简化为共素数 a/d 和 c/b。
如果询问我可以创建一个函数 neg() 来避免溢出a
orb
是否 <0 或 > 0,我总是可以级联。不是最漂亮的。除了那个可怕的选择,T neg(T x){if (x > 0) return -x; else return x;},
- 我可以采取 gcd 的 abs(x) 和任何类似情况(任何地方 x > LONG_MIN)
我不确定 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
检测签名操作中的溢出。这可能有点太笼统了,有很多分支,并且没有讨论如何避免溢出。
答案中提到的CERT 规则是一个很好的起点,但再次只讨论如何检测。
其他答案太笼统了,我想知道对于我正在查看的案例是否有更具体的答案。