浮点比较奇怪的结果

时间:2009-04-03 12:48:12

标签: c++ floating-point

我有这个简单的测试:

double h;
...
// code that assigns h its initial value, used below
...
if ((h>0) && (h<1)){
 //branch 1 -some computations
}
else{
 //branch 2- no computations
}

我列出了我的值,因为我得到了一些非常奇怪的结果,例如: h = 1然后到达第一个分支,我不明白为什么因为如果h = 1我想要计算branch2 我对这么明显的东西感到困惑吗?


编辑:

这是我计算然后使用h的方式:

double* QSweep::findIntersection(edge_t edge1,edge_t edge2) {  
point_t p1=myPoints_[edge1[0]];
point_t p2=myPoints_[edge1[1]];
point_t p3=myPoints_[edge2[0]];
point_t p4=myPoints_[edge2[1]];

double xD1,yD1,xD2,yD2,xD3,yD3,xP,yP,h,denom;
double* pt=new double[3];

// calculate differences  
xD1=p2[0]-p1[0];  
xD2=p4[0]-p3[0];  
yD1=p2[1]-p1[1];  
yD2=p4[1]-p3[1];  
xD3=p1[0]-p3[0];  
yD3=p1[1]-p3[1];    

xP=-yD1;
yP=xD1;
denom=xD2*(-yD1)+yD2*xD1;
if (denom==0) {
    return NULL;
}
else{
h=(xD3*(-yD1)+yD3*xD1)/denom;
}
std::cout<<"h is"<<h<<endl;
if (h < 1) std::cout<<"no"<<endl;
else std::cout<<"yes"<<endl;
if (h==1) {
    return NULL;
}
else{
if ((h>0)&&(h<1)){
    pt[0]=p3[0]+xD2*h;  
    pt[1]=p3[1]+yD2*h;
    pt[2]=0.00;
}
else{
    return NULL;
}
}


return pt;  

}


编辑:

好的,很明显我应该如何重新制定病情。

自:

double h;
if (h==1){
   //computations here
}

要:

double h;
if (abs(h-1)<tolerance){
  //computations here
}

当我使用双号时。

但我该如何改编呢?

double h;
if (h<1){
   //computations here
}

10 个答案:

答案 0 :(得分:14)

由于h是双精度数,因此它可能已接近1以打印为1,但它实际上小于1,因此比较成功。 Floating-point numbers做了很多。

答案 1 :(得分:5)

通过printing it out with maximum precision检查h的实际值。您可能会发现它实际上略低于1.0。

我将以下代码作为测试运行

#include <iostream>

int main()
{
    double h = 1.0;
    if((h>0) && (h<1))
    {
        std::cout << "first branch" << std::endl;
    }
    else
    {
        std::cout << "second branch" << std::endl;
    }
}

并且输出是“第一个分支”(在Ubuntu 8.10上使用g ++ 4.3.2),但Indeera在评论中提到,在使用VS2005编译的Windows XP上运行的相同代码给出输出“第二个分支”(谢谢,Indeera )。

您可以更改代码,将h和0.0以及h和1.0之间的差异与某个较小的delta值进行比较。

double allowedDelta = 0.000001;

if( ((h - 0.0) > allowedDelta) && ((1.0 - h) > allowedDelta) )
... // h is between 0.000001 and 0.9999990

请注意,在这种特殊情况下,“(h - 0.0)”可以替换为“h”。我将其保留为具有说明价值的方式。

另请注意,如果您只进行一次比较,则需要将delta与h与某些常量之差的绝对值进行比较。由于您在这里检查范围,因此两个比较ANDed一起构成了一个特殊情况,您可以绕过abs的使用。如果h是负值或某个大于1.0的正值,则它将超出范围并且无法通过上述两个测试中的一个。

答案 2 :(得分:4)

简短的故事:您的测试不正确,因为浮点数的行为与您可能期望的不同。特别是像“denom == 0”这样的东西是有问题的。

Sun已经足够好在线提供这篇论文了:

What Every Computer Scientist Should Know About Floating Point Arithmetic

与广告完全一致。根据你的背景,它将是一个简单的阅读或大量的工作,但对于任何使用浮动的程序员来说,它确实值得花时间。

添加评论:我并不是说每个程序员都会轻易理解该论文中的所有内容。阅读它虽然至少可以更好地了解浮动实际上是什么,问题是什么,以及如何正确处理事情的一些提示。

如果你想要正确地进行大量的数字工作,你将不得不阅读各种技术,但这将是一本教科书(或几个)的材料。这里的评论已经指出了一些基础知识,并与更多

相关联

答案 3 :(得分:3)

比较浮点值时始终允许舍入错误。而不是测试平等,这样的事情通常是你想要的:

if (abs(x - y) < epsilon) ...

其中epsilon是一个适当的小值,如0.00001。

浮点数学不精确有几个原因。首先,并非所有值都可以准确表示(例如0.1,不能用二进制浮点数表示,与1/3不能用小数表示的方式相同)

另一个是浮点只使用固定数量的有效数字(相对于小数点“浮动”,因此名称)。所以在大数字上,小分数有效地被截断了。永远不要认为浮点计算会返回准确的结果。

答案 4 :(得分:1)

这可能是一个精确的问题。 H可能不完全是1,但非常接近它。你能否发布一些关于你想要做什么的更多信息,例如,H的价值来自哪里,它在哪里?

答案 5 :(得分:0)

这可能与C / C ++中的双精度为64位这一事实有关,但计算可以以更高的精度完成(cpu的浮点寄存器更宽(96位),所以甚至不是像cos(x)== cos(x)可能是真的。

参考:http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18

答案 6 :(得分:0)

原因是浮点数不是您在变量中保存的数字的真实表示。 (反对BCD [二进制编码小数])

您可以在此处查看定义: Floating Point Wikipedia

所以问题是某些数字不能用给定的一组位来表达。 (如果你可以无限地添加位,你可以) 诀窍在于,在大多数情况下,保存的数字和预期数量之间的差异非常小。在实践中,你有一些可能导致问题的极端情况。 例如,这就是您不应该构建财务软件并使用浮点数进行货币计算的原因。您可以很容易地发现明显的差异,这是税务局不喜欢的。

因此,为了比较浮点数,您应该始终应用某种适用于您的应用程序的阈值。类似的东西:

if(a==b) 

变为

if(abs(a-b)<threshold)
编辑:正如大卫在评论中提到的那样,你仍然会遇到像pi,1/3,...等问题。 但是你至少可以存储数字而不会损失你输入系统的精度。由于计算机的内存有限,您可以随时构建不能依赖精确表示的极端情况......

刚看到你对文字的编辑,所以这是下一个编辑:

if(a<1)

在某种程度上更难,因为你不知道它是否仅仅是数字表示是错误的还是它只是一个接近1的实数值。它实际上取决于算法的要求。如果出现小错误,请执行以下操作:

if(a < 1-threshold)

如果不行,那么你必须使用另一个没有遇到问题的变量类型。

答案 7 :(得分:0)

好的,你已经发布了代码。你是通过一系列算术运算来计算h,看起来像是相当任意的数字。这意味着你将非常接近h的理想值,但不是非常接近正确的值。

这意味着您需要进行近似比较。测试(h == 1.0)只会偶然成功;尝试(fabs(h - 1.0)&lt; 1e-10)或类似的东西(使用const double代替公差的幻数)。为其他比较做出适当的更改。

答案 8 :(得分:0)

您可能对GDC 2005上的Numerical Robustness for Geometric Calculations(又名“EPSILON is 0.00001!”)发表的演讲非常感兴趣。

答案 9 :(得分:-1)

如果你必须在支票中使用浮点数,将它们四舍五入并将其存储在例如整数中。 1f可以是1.0000000000000000000000000000000001或0.99999999999999999999999999999999999999