c++ 浮點(diǎn)數(shù)相等判斷背后的陷阱和原理

我們應(yīng)該注意的是:
在我們的代碼中,涉及到浮點(diǎn)數(shù)比較的問題,千萬不要嘗試去比較兩數(shù)是否相等。
在講解問題、解決方法以及背后的原理,我們先來看一個(gè)例子:

int main()
{
    double epsilon=0.001;
    double d1=2.334;
    double d2=2.335;
    
    cout << "epsilon is: " << epsilon << endl;
    cout << "d2-d1 is: " << d2-d1 << endl;
    
    if ((d2 - d1) == epsilon){
        cout << "Equal!" << endl;
    }
    else{
        cout << "Not equal!" << endl;
    }

    return 0;
}

運(yùn)行結(jié)果如下:

epsilon is: 0.001
d2-d1 is: 0.001
Not equal!
Program ended with exit code: 0

為了理解為什么程序結(jié)果和我們的期望不一樣,我們?cè)賮砜纯聪旅娴拇a以及相應(yīng)的輸出:

int main()
{
    double epsilon=0.001;
    double d1=2.334;
    double d2=2.335;
    
    cout<<"epsilon is: "<< setprecision(20) << epsilon<<endl;
    cout<<"d2-d1   is: "<< setprecision(20) << d2-d1 <<endl;
    
    if ((d2 - d1) == epsilon){
        cout << "Equal!" << endl;
    }
    else{
        cout << "Not equal!" << endl;
    }

    return 0;
}

輸出結(jié)果如下:

epsilon is: 0.0010000000000000000208
d2-d1   is: 0.00099999999999988986588
Not equal!
Program ended with exit code: 0

從上面我們可以很容易的得知到一個(gè)事實(shí):浮點(diǎn)數(shù)在計(jì)算機(jī)中不一定是精確表示的。所以我們得到的結(jié)論是:
永遠(yuǎn)不要嘗試去比較兩個(gè)浮點(diǎn)數(shù)是否相等

背后原理機(jī)制

根據(jù)IEEE(Institute of Electrical and Electronic Engineers )754標(biāo)準(zhǔn),標(biāo)準(zhǔn)浮點(diǎn)數(shù)的格式如下:


其中n是浮點(diǎn)數(shù),s是符號(hào)位,m是尾數(shù),e是階數(shù)。
由上面我們可以得知,任何存儲(chǔ)在計(jì)算機(jī)中的浮點(diǎn)數(shù)都可以用(-1)s * m * 2e得到。因?yàn)槎M(jìn)制數(shù)無法精確表示一些十進(jìn)制數(shù)的小數(shù)(類似于十進(jìn)制數(shù)無法精確表示0.33333333……無限循環(huán)數(shù)一樣。其中0.33333333333是三進(jìn)制小數(shù)(0.1)3。這也就是很多浮點(diǎn)數(shù)在計(jì)算機(jī)中不能被精確表示的原因。
按照上述原理,我們可以來驗(yàn)證下計(jì)算機(jī)是否能夠精確表示能用整數(shù)乘以2的冪表示的十進(jìn)制數(shù):

int main()
{
    double float_num = 1.5;
    cout << "float_num is: " << setprecision(20) << float_num << endl;
    
    return 0;
}

運(yùn)行結(jié)果如下:

float_num is: 1.5
Program ended with exit code: 0

結(jié)果和我們預(yù)期的一樣。因此我們的結(jié)論是:
除了能用2的指數(shù)冪乘以整數(shù)表示的浮點(diǎn)數(shù)能夠被精確的表示外,其余的浮點(diǎn)數(shù)都是近似表示的。
在c++中,兩個(gè)浮點(diǎn)數(shù)相差在DBL_EPSILON之內(nèi)的數(shù)都認(rèn)為是相等的:

#include <float.h>
#include <iostream>
using namespace std;

int main()
{
    cout << DBL_EPSILON << endl;
    
    return 0;
}

結(jié)果:

2.22045e-16
Program ended with exit code: 0

解決方案

為了避免因?yàn)椴痪_導(dǎo)致的錯(cuò)誤,我們可以用容錯(cuò)閾值來解決這個(gè)問題:

if (abs(d2 - d1) < epsilon){
        cout << "Equal!" << endl;
    }

或者

if(abs(a - b) <= epsilon * abs(a)){
       cout << "Equal!" << endl;
}

注意:
c++中的abs函數(shù)已經(jīng)被重載,因此可以適用int, long int, float等各種類型。如果是c請(qǐng)調(diào)用fabs用于浮點(diǎn)數(shù)的絕對(duì)值。
上述兩種方法中,epsilon為精度值。這個(gè)精度值是我們選擇來判斷兩個(gè)數(shù)是否足夠接近以至于可以認(rèn)為是相等的。
我們不建議用一個(gè)常數(shù)值來做epsilon。因?yàn)殡S著比較數(shù)的改變,epsilon也應(yīng)該隨之改變來適應(yīng)精度要求。例如我們選擇了epsilon = 0.01。當(dāng)兩個(gè)比較數(shù)的數(shù)量級(jí)都在109以上但是二者的差值是0.02。0.02對(duì)于差值對(duì)于兩個(gè)數(shù)而言可以忽略不計(jì),因此二者就是近似相等的。然而依據(jù)0.01做判斷,二者被判定為不相等。
Doug Gwyn建議用相對(duì)差值函數(shù)。當(dāng)兩個(gè)比較數(shù)完全相同時(shí),函數(shù)返回值為0。否則函數(shù)的返回值是兩個(gè)差值的絕對(duì)值比去兩者中較大的數(shù)。

#define Abs(x)    ((x) < 0 ? -(x) : (x))
#define Max(a, b) ((a) > (b) ? (a) : (b))

double RelDif(double a, double b)
{
    double c = Abs(a);
    double d = Abs(b);

    d = Max(c, d);

    return d == 0.0 ? 0.0 : Abs(a - b) / d;
}

用法如下:

if(RelDif(a, b) <= TOLERANCE) ...
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容