前些天參加深信服面試,面試官問了這樣一個問題:浮點數的大小比較爲何不能用等號?那時就沒回答好,由於本身一直把「浮點數大小比較不可以用等號」當作默認事實而沒有去深究其背後原理。本文將試着解釋這個事實。ios
計算機中是如何存儲和表達數字的?對於整數,狀況比較簡單,直接按照數學中的進制轉換方法處理便可,即連續除以2取餘。這並非難點,真正的難點在於小數是如何轉換爲二進制碼(即浮點數)的(注意區別小數和浮點數)。面試
固然,從數學的角度來說,十進制的小數能夠轉換爲二進制小數(整數部分連續除2,小數部分連續乘2),例如125.125D=1111101.001B,但問題在於計算機根本就不認識小數點「.」,更不可能認識1111101.001B。那麼計算機是如何處理小數的呢?緩存
歷史上計算機科學家們曾提出過多種解決方案,最終得到普遍應用的是IEEE 754標準中的方案,目前最新版的標準是IEEE std 754-2008。該標準提出數字系統中的浮點數是對數學中的實數(小數)的近似,同時該標準規定表達浮點數的0、1序列被分爲三部分(三個域):優化
以32位單精度浮點數爲例,其具體的轉換規則是:首先把二進制小數(補碼)用二進制科學計數法表示,好比上面給出的例子1111101.001=1.111101001*2^6。符號位sign表示數的正負(0爲正,1爲負),故此處填0。exponent表示科學計數法的指數部分,請務必注意的是,這裏所填的指數並非前面算出來的實際指數,而是等於實際指數加上一個數(指數偏移),偏移量爲2^(e-1)-1,其中e是exponent的寬度(位數)。對於32位單精度浮點數,exponent寬度爲8,所以偏移量爲127,因此exponent的值爲133,即10000101。以後的fraction表示尾數,即科學計數法中的小數部分11110100100000000000000(共23位)。所以32位浮點數125.125D在計算機中就被表示爲01000010111110100100000000000000。spa
對於32位單精度浮點數,sign是1位,exponent是8位(指數偏移量是127),fraction是23位。對於64位雙精度浮點數,sign是1爲,exponent是11位(指數偏移量是1023),fraction是52位。.net
用程序能夠看出小數在計算機中是如何表示的(即浮點數):3d
1 #include <iostream> 2 #include <bitset> 3 using namespace std; 4 5 int main() 6 { 7 float input; 8 cin >> input; 9 unsigned long long nMem = *(unsigned long long *)&input; 10 bitset<32> myBit(nMem); 11 cout << myBit << endl; 12 13 return 0; 14 }
程序運行結果:code
下文將從幾個方面來探索這個問題:blog
請見程序:ip
1 #include <iostream> 2 #include <cmath> 3 using namespace std; 4 5 int main() 6 { 7 float a = (float)0.1; 8 float b = (float)0.1; 9 if(a == b) 10 cout << "a == b" << endl; 11 else 12 cout << "a == b" << endl; 13 14 float c = (float)0.1; 15 double d = (double)0.1; 16 if(c == d) 17 cout << "c == d" << endl; 18 else 19 cout << "c != d" << endl; 20 21 float e = (float)0.1; 22 float f = (double)0.1; 23 if(abs(e - f) < 0.0001) 24 cout << "e == f" << endl; 25 else 26 cout << "e != f" << endl; 27 28 return 0; 29 }
程序運行結果以下:
從該例子能夠看出,對於同一個小數,當用不一樣精度表示時,結果是不同的,不能直接用等號比較大小。
按照博文計算機中基本類型float值表示和大小比較問題的說法:
即便在精度相同的狀況下,比較也可能會出問題。由於在運算過程當中會將內存(或高速緩存)中的值加載到CPU浮點寄存器(80 bit擴展精度)中,而後再進入CPU浮點計算單元進行計算,計算結果寫回浮點寄存器,而後寫回內存(或高速緩存)。從內存到浮點寄存器,浮點數的精度會擴展,從浮點寄存器到內存,浮點數的精度會下降(精度擴展一般沒問題,但若是精度下降了,極可能值會發生變化,出現截斷),而浮點運算的結果因爲下面還要使用因此暫時保存在浮點寄存器中留待下次使用(沒有及時寫回內存,這是一種優化策略),從而致使數據並非內存中和內存中的數據比較而是浮點寄存器中的值和內存中的值進行比較,而不管內存中是float類型仍是double類型,其精度和浮點寄存器精度都不相同,從而致使比較結果是不相等。
詳細的可參考維基百科條目Extended precision。