我发现了一个有趣的浮点问题。我必须在我的代码中计算几个平方根,表达式如下:
sqrt(1.0 - pow(pos,2))
其中pos在循环中从-1.0到1.0。 -1.0对于pow来说很好,但是当pos = 1.0时,我得到了一个-nan。做一些测试,使用gcc 4.4.5和icc 12.0,输出
1.0 - pow(pos,2) = -1.33226763e-15
和
1.0 - pow(1.0,2) = 0
或
poss = 1.0
1.0 - pow(poss,2) = 0
显然,第一个会产生问题,而是消极的。任何人都知道为什么pow返回小于0的数字?完整的违规代码如下:
int main() {
double n_max = 10;
double a = -1.0;
double b = 1.0;
int divisions = int(5 * n_max);
assert (!(b == a));
double interval = b - a;
double delta_theta = interval / divisions;
double delta_thetaover2 = delta_theta / 2.0;
double pos = a;
//for (int i = 0; i < divisions - 1; i++) {
for (int i = 0; i < divisions+1; i++) {
cout<<sqrt(1.0 - pow(pos, 2)) <<setw(20)<<pos<<endl;
if(isnan(sqrt(1.0 - pow(pos, 2)))){
cout<<"Danger Will Robinson!"<<endl;
cout<< sqrt(1.0 - pow(pos,2))<<endl;
cout<<"pos "<<setprecision(9)<<pos<<endl;
cout<<"pow(pos,2) "<<setprecision(9)<<pow(pos, 2)<<endl;
cout<<"delta_theta "<<delta_theta<<endl;
cout<<"1 - pow "<< 1.0 - pow(pos,2)<<endl;
double poss = 1.0;
cout<<"1- poss "<<1.0 - pow(poss,2)<<endl;
}
pos += delta_theta;
}
return 0;
}
答案 0 :(得分:8)
当你在循环中继续增加pos时,舍入错误会累积,在你的情况下,最终值> 1.0。取而代之的是,通过每轮的乘法计算pos,以获得最小量的舍入误差。
答案 1 :(得分:4)
问题在于浮点计算不准确,而1 - 1 ^ 2可能会产生较小的负面结果,从而产生无效的sqrt
计算。
考虑限制你的结果:
double x = 1. - pow(pos, 2.);
result = sqrt(x < 0 ? 0 : x);
或
result = sqrt(abs(x) < 1e-12 ? 0 : x);
答案 2 :(得分:2)
setprecision(9)
会导致四舍五入。使用调试器来查看实际值是什么。除此之外,至少将精度设置为超出您正在使用的类型的可能大小。
答案 3 :(得分:0)
使用双精度计算时几乎总会出现舍入误差,因为double类型只有15个有效十进制数字(52位),并且很多十进制数不能转换为二进制浮点数而不进行舍入。 IEEE标准包含了很多努力来保持这些错误,但原则上它并不总能成功。有关详细介绍,请参阅this document
在您的情况下,您应该在每个循环上计算pos并舍入到14或更少的数字。这应该给你一个干净的0为sqrt。
您可以将循环中的pos计算为
pos = round(a + interval * i / divisions, 14);
圆形定义为
double round(double r, int digits)
{
double multiplier = pow(digits,10);
return floor(r*multiplier + 0.5)/multiplier;
}