我們應(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) ...