sqrt(1.0 - pow(1.0,2))返回-nan

时间:2010-12-15 18:12:58

标签: c++

我发现了一个有趣的浮点问题。我必须在我的代码中计算几个平方根,表达式如下:

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;
 }

4 个答案:

答案 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;
}