刨根問底:C++中浮點型變數(float, double)的比較問題。
首先,讓我們先來看一段程式碼:
#include <iostream> #include <iomanip> int main() { using namespace std; cout<<setprecision(17); float num1 = 1.1; double num2 = 1.1; if (num1 == num2) cout << "yes"<<endl; else cout << "no"<<endl; cout<<num1<<";"<<num2<<endl; return 0; }
程式碼很簡單,比較下num1和num2是否相等,那麼是否相等呢?看字面值是一樣的,理論上確實應該相等,但實際上卻不是。為什麼?這裡涉及到了一個浮點型資料的精度和表示問題。
好,我們就來談談這兩個問題。
1. 精度(Precision)
精度可以理解成在不丟失資料的前提下,可以儲存多少位資料的能力。比如,1/3這個分數,如果用小數表示應該是0.333333...的無線迴圈,因此若想在記憶體中表示這個資料,需要無限的儲存空間,這顯然是不可能的。我們常見的float和double型分別是4 bytes和8 bytes,都是隻能儲存有限位的小數,float和double分別能表示6-7個小數位和15-16個小數位。
2. 進位(Rounding)
這裡的進位問題是值10進位制轉化成2進位制的過程。我們都知道十進位制整數轉化成二進位制的方法是不斷的除以2,直到除盡為止,而十進位制小數轉化成二進位制則是不斷的乘以2,直到值為1為止。這裡面有個問題,整數不斷的除以2,總有最後為0的時候,但小數乘以2,卻不一定得到1,也就是說,並不是所有十進位制小數轉成有限的二進位制資料,而因為上面提到的精度問題,因此,會出現精度缺失問題,這種情況,我們稱之為“進位錯誤”,英文是Rounding error。
基於以上兩點,上面那段程式碼就不難理解了。
num1和num2雖然字面值一樣,但是在記憶體中的儲存資料完全不同,在儲存1.1的過程中,精度缺失和進位錯誤同時出現,因此num1和num2做==比較的時候,遵循精度保留原則,num1會被編譯器premote成double型別,但由於本身精度是7位小數,強制提升為15位小數後,後面補位的數字都是隨機的,這點可以在最後的輸出中可以看到(為了以便觀察,輸出的精度設了17位)。
那麼,如何解決這種問題?
1. 可以設定一個可接受的誤差值,當誤差小到一定程度的時候,就可以忽略了。譬如,abs(num1-num2)<0.00001。
2. 可以給高精度型別強制降精度,比如用這種方式:num1 == static_cast<float>(num2)。但注意,不要給低精度型別強制提升精度,如果這樣就等於和編譯器做的一樣了,結果也是一樣的。