我理解的方式是:当在c ++中用双精度减去两个double
个数时,它们首先被转换为有效数字,从指数幂的2倍开始。如果减去的数字在有效数字中具有相同的指数和许多相同的数字,则会出现错误,从而导致精度损失。为了测试我的代码,我编写了以下安全添加功能:
double Sadd(double d1, double d2, int& report, double prec) {
int exp1, exp2;
double man1=frexp(d1, &exp1), man2=frexp(d2, &exp2);
if(d1*d2<0) {
if(exp1==exp2) {
if(abs(man1+man2)<prec) {
cout << "Floating point error" << endl;
report=0;
}
}
}
return d1+d2;
}
然而,测试这个我注意到一些奇怪的事情:似乎实际错误(不是函数报告错误而是计算产生的实际错误)似乎取决于减去数字的绝对值而不仅仅是数字有效数字中的相等数字...
例如,使用1e-11
作为精度prec
并减去以下数字:
1)9.8989898989898-9.8989898989897:该函数报告错误,我得到的值非常不正确9.9475983006414e-14
2)98989898989898-98989898989897:该函数报告错误但我得到了正确的值1
显然我误解了一些事情。有什么想法吗?
答案 0 :(得分:6)
如果减去两个几乎相等的浮点值,结果将主要反映低位的噪声。这里几乎相等的不仅仅是相同的指数和几乎相同的数字。例如,1.0001和1.0000几乎相等,减去它们可以通过这样的测试捕获。但1.0000和0.9999的数量完全相同,并且不会被这样的测试所捕获。
此外,这是不安全添加功能。相反,它是对设计/编码错误的事后检查。如果你减去两个非常接近的值,那么噪音很重要你就犯了一个错误。修复错误。我并不反对使用这样的东西作为调试辅助工具,但是请把它称之为暗示它就是这样的东西,而不是暗示浮点加法存在固有的危险。此外,将检查放在添加函数内似乎是过分的:断言这两个值不会导致问题,然后是一个普通的旧浮点加法,可能会更好。毕竟,代码中的大多数添加都不会导致问题,您最好知道问题所在的位置;把断言置于问题点。
答案 1 :(得分:2)
+1 Pete Becker的答案。
请注意,exp1!= exp2
也可能出现退化结果的问题例如,如果你减去
1.0-0.99999999999999
所以,
bool degenerated =
(epx1==exp2 && abs(d1+d2)<prec)
|| (epx1==exp2-1 && abs(d1+2*d2)<prec)
|| (epx1==exp2+1 && abs(2*d1+d2)<prec);
您可以省略对d1 * d2&lt; 0的检查,或者保留它以避免整个测试...
如果你还希望处理退化的非规范化浮点数的精度损失,那将会更复杂一些(好像有效数字的位数更少)。
答案 2 :(得分:1)
很容易证明,对于IEEE 754浮点运算,如果x / 2 <= y <= 2x,则计算x - y是一个精确的运算,并且将正确地给出精确结果任何舍入误差。
如果加法或减法的结果是非规范化数,则结果总是精确。