我一直在努力创建一个尽可能完整的Fraction类,以自己学习C ++,类和相关的东西。除此之外,我想确保某种程度的保护"反对浮点异常和溢出。
避免在常见操作中发现的算术运算中出现溢出和浮点异常,从而消耗最少的时间/内存。如果无法避免,那么至少要检测它。
另外,我的想法是不要投射到更大的类型。这会产生一些问题(比如可能没有更大的类型)
溢出+, - ,*,/,pow,root
操作大多是直截了当的(
a
和b
是Long
):
- a + b:如果LONG_MAX - b>然后有溢出。 (不够。
a
或b
可能是否定的)- 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
当x = LONG_MIN(或等效)
时,abs(x)溢出这很有趣。每个带符号的类型都有一个[-x-1,x]范围的可能值。 abs(-x-1)= x + 1 = -x-1因为溢出。这意味着存在abs(x)
在应用分子/ gcd(分子,分母)时找到。有时gcd返回-1,我得到一个浮点异常。
在Fraction算术中,有时候我可以做额外的简化检查:为了解决a / b * c / d(联合素数),我可以先减少共同素数a / d和c / b。
a
或b
是< 0或>,我可以随时进行级联0.不是最漂亮的。除了糟糕的选择,T neg(T x){if (x > 0) return -x; else return x;},
我不确定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
Detect overflow in signed operations。这可能有点过于笼统,有很多分支,并没有讨论如何避免溢出。
答案中提到的CERT rules是一个很好的起点,但我们只讨论如何检测。
其他答案过于笼统,我想知道是否有任何答案更具体针对我所查看的案例。
答案 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
(或类似的机制)。