比较双精度浮点数

15

我正在编写一个程序,其中包含一个while循环,读取两个double数,并将它们打印出来。该程序还会输出哪个数更大,哪个数更小。

以下是我目前的代码:

int main()
{

                                    // VARIABLE DECLARATIONS 

    double a;
    double b;

    while (a,b != '|')              //WHILE A & B DO NOT EQUAL '|'
    {
        cin >>a >>b;
        cout << a << b << "\n" ;


        if (a<b)                    //IF A<B: SMALLER VALUE IS A
        cout << "The smaller value is:" << a << endl 
             << "The larger value is:" << b << endl ;

        else if (b<a)               //ELSE IF B<A 
            cout << "The smaller value is:" << b << endl 
                 << "The larger value is:" << a << endl ;
        else if (b==a)
            cout << "The two numbers you entered are equal." << "\n" ;

    }
}
下一步是,如果这两个数字相差小于1.0/10000000,则让程序输出“the numbers are almost equal”。我该如何做到这一点?

6
如果这是一份作业,请添加标签homework - Johann Gerell
3
有趣的是那种气味可以传播得那么远... - Michael Goldshteyn
9
@user484955:你真的知道 while (a,b != '|') 是什么意思吗? - Kiril Kirov
2
你需要使用 while (a != '|' && b != '|')。而你现在使用了逗号运算符,它会先计算 a,然后计算 b != '|',最终结果是 b != '|'。换句话说,你只有 while (b != '|'),忽略了 a。至于为什么要这样比较,我还不清楚。 - GManNickG
1
@jaked122:主要是因为1. 它非常慢,而且2. "返回代理对象并设置failbit"的失败模式很糟糕,2a. 调试应用程序为什么突然停止打印或读取数据也很麻烦。 - Billy ONeal
显示剩余5条评论
8个回答

28

以下是我如何测试相等性的方法,没有“容差因素”:

if (
    // Test 1: Very cheap, but can result in false negatives
    a==b || 
    // Test 2: More expensive, but comprehensive
    std::abs(a-b)<std::abs(std::min(a,b))*std::numeric_limits<double>::epsilon())
  std::cout << "The numbers are equal\n";

解释

第一个测试是一个简单的比较。当然,我们都知道比较双精度值可能会导致它们被视为不相等,即使它们在逻辑上是等价的。

双精度浮点值可以保存一个数字的最高十五位(实际上约为≈15.955位)。因此,如果(大约)它们的前十五位匹配,我们希望将两个值称为相等。换句话说,如果它们彼此之间相差不到一个缩放的ε,则我们希望将它们称为相等。这正是第二个测试所计算的。

您可以选择添加更多余地,以应对由于迭代计算而产生的更重要的浮点误差。为此,请将误差因素添加到第二个测试比较的右侧:

double error_factor=2.0;

if (a==b ||         
    std::abs(a-b)<std::abs(std::min(a,b))*std::numeric_limits<double>::epsilon()*
                  error_factor)
  std::cout << "The numbers are equal\n";

我无法给你一个固定的error_factor值,因为它取决于计算中出现的错误量。但是,通过一些测试,你应该能够找到一个适合你的应用程序的合理值。请记住,仅基于猜测添加(任意的)误差因素将把你带回到人为调整因素的领域。 总结 你可以将以下测试封装到一个(内联)函数中:
inline bool logically_equal(double a, double b, double error_factor=1.0)
{
  return a==b || 
    std::abs(a-b)<std::abs(std::min(a,b))*std::numeric_limits<double>::epsilon()*
                  error_factor;
}

我认为你需要将“/1.e-15”替换为“/1.e15”,否则比较运算符右侧的值会非常大。 - regomodo
@MichaelGoldshteyn:你能解释一下 std::abs(a-b)<std::min(a,b)/1.e15 背后的原因吗? - Mihai Bişog
2
@Mihai,这与double可以表示的最大整数位数有关,即15位。因此,如果减法结果的绝对值小于由于不精确表示而产生的减法可以产生的最小值,则称原始数字不相等。 - Michael Goldshteyn
@MichaelGoldshteyn:感谢您抽出时间写下这份澄清说明。 - Mihai Bişog
1
很棒的解决方案。这是我找到的第一个深入了解问题并提供全面解决方案的方案。谢谢。 - mark sabido

10

std::abs(a - b) < 0.000001

当然,可以用任何你认为“几乎相等”的常数替换这个值。


2
另外,请注意,“错误”会随着a和b的大小而增加。 - Fredrik
@Fredrik:没错,但是OP指定了“两个数字之间的差小于某个常数”。当然,你可以组合一些东西来检查值的指数是否相同,然后仅在曼蒂萨上使用类似的常量。 - Billy ONeal
@Billy ONeil:是的,我只是想添加额外的信息。我看到太多人编写的代码没有基本的理解,为什么“小于1e-10”并不总是有效,所以最好保持安静 :-)。我没想让你改变你的答案,我只是觉得写一个更完整的答案有点过头了。比较只是问题的一面,当你进行加法、乘法等操作时,你会看到另一面。也许我错误地认为OP还没有那种知识/经验。 - Fredrik
@Michael:是的,这就是为什么我给你使用std::numeric_limits加了+1的原因 :P - Billy ONeal

4

只需测试它们是否相差少于该金额即可 :)

if ( std::abs(a - b) < 1.0 / 10000000 )
  cout << "The numbers are almost equal.\n";

我尝试了这个,但是出现了一个错误,错误信息为“调用重载函数'abs(double)'不明确”。 - Capkutay
2
Capkutay:你有包含<cmath>吗?这是std::abs工作所必需的。你可能在某个地方使用了using namespace std;,这是不好的实践,可能会导致std::abs和全局命名空间中的abs之间发生冲突。(abs允许但不要求放在全局命名空间中,因为它是从C继承而来)通过删除using namespace std;并适当限定对标准库的调用来解决这个问题。 - Billy ONeal

2
if (a * 1.0000001 > b  &&  a < b*1.0000001)

您可以添加一个错误值(例如1.0 / 10000000.0),但通常最好使用乘数,这样比较就可以达到相同的精度水平。


1
其他答案是寻找绝对误差值。这可能会更快,但只有在您知道两个值的数量级时才准确。 - winwaed

2

1
abs(a - b) < 1.0 / 10000000

1

我也在阅读这本书,由于我们没有涉及到std::abs,所以我做了类似的事情:

int main()
{
double i1,i2;
while(cin>> i1 >> i2){

if (i1<i2) {
        if ((i2-i1)<=0.0000001)  cout << "Almost equal!"<<endl;
        else  cout << "the smaller value is: "<< i1 <<  " the larger value is: " << i2 <<endl;
}
if (i1>i2) {
        if ((i1-i2)<=0.0000001)  cout << "Almost equal!"<<endl;
        else  cout << "the smaller value is: "<< i2 <<  " the larger value is: " << i1 <<endl;
}


else if (i1==i2) cout << "the value : "<< i1 <<  " And the value : " << i2 << "  are equal!"<<endl;

}
}

1

如果你想让测试与a和b一起扩展,可以尝试测试abs(a/b-1) < e,其中e是你喜欢的微小数字,如0.001。但是这个条件实际上在a和b中是不对称的,因此它可能会导致说a接近b,但b并不接近a。那就糟糕了。更好的方法是使用abs(log(a/b)) < e,其中e再次是你喜欢的微小数字。但是对数会带来额外的计算量,更不用说吓坏了所有的本科生。


  1. "favorite tiny number" 应该是 std::numeric_limits<double>::epsilon()
  2. 无论如何,使用对数可能不是处理这个问题的最佳方式。如果您想要类似于此的比较,应该检查符号和指数是否相等,然后比较有效数字并允许一些误差值。(使用 frexp 来完成这个操作 -- 它应该比 loglog10 更快)。
- Billy ONeal
这是一个比较好的答案,因为它相对于a和b进行了比较,但最好使用乘法而不是除法。因为当b为零时,除法会出现问题。abs(a-b) < abs(epsilonb)(或对称性可用epsilon(a+b)/2) - CashCow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接