浮點數的相等比較

浮點數的相等比較

今天羣裏邊一哥們兒——哦,確切地說,應該是一姊妹——問到了Python中怎樣判斷一個浮點數的小數部分是否是0,本意是要找和C語言中的fmod函數相同功能的函數的,在Python中在math模塊中有這個函數,但是卻挑起了一個浮點數精度的話題。算法

忽然想到偶們學習C語言的時候老師講到過,整數的相等比較能夠直接使用==來判斷,可是浮點數的比較不能簡單的用==來比較。那怎麼對浮點數是否相等進行判斷呢?ide

Google了一番,又找到了那個經典的方法:利用差值的絕對值的精度來判斷。函數

具體就是:f1f2是兩個浮點數,precision是咱們本身設置的精度,好比1e-6學習

則能夠用 fabs(f1-f2)<=precision 來判斷f1f2是否相等。spa

若是要求更高的精度,則把precision定得更小就好了。內存

 

 

網上有一位大牛講,這個方法還存在問題:ci

「首先,precision是一個絕對的數據,也就是偏差分析當中所說的絕對偏差,使用一個固定的數值,對於float類型能夠表達的整個數域來講是不能夠的。好比precision取值爲0.0001,二f1f2的數值大小也是0.0001附近的,那麼顯然不合適。另外對於f1f2大小是10000這樣的數據的時候,它也不合適,由於1000010001也能夠真僞是相等的呢。適合它的狀況只是f1或者f21或者0附近的時候。」it

 

這位大牛給出了這樣的解決方法:io

asm

既然絕對偏差不能夠,那麼天然的咱們就會想到了相對偏差

bool IsEqual(float a, float b, float relError ) {

       return ( fabs ( (a-b)/a ) < relError ) ? true : false;

}

這樣寫還不完善,由於是拿固定的第一個參數作比較的,那麼在調用

IsEqual(a, b, relError ) IsEqual(b, a, relError ) 的時候,可能獲得不一樣的結果

同時若是第一個參數是0的話,就有多是除0溢出

這個能夠改造

把除數選取爲ab當中絕對數值較大的便可

bool IsEqual(float a, float b, relError )

{

      if (fabs(a)<fabs(b)) return  ( fabs((a-b)/a) > relError ) ? true : false;

      return  (fabs( (a-b)/b) > relError ) ? true : false;

};

 

使用相對偏差就很完善嗎?

也不是, 在某些特殊狀況下, 相對偏差也不能表明所有

好比在判斷空間三點是否共線的時候,使用判斷點到另外兩個點造成的線段的距離的方法的時候

只用相對偏差是不夠的,應爲線段距離可能很段,也可能很長,點到線段的距離,以及線段的長度作綜合比較的時候,須要相對偏差和絕對偏差結合的方式才能夠

相對完整的比較算法應該以下:

bool IsEqual(float a, float b, float absError, float relError )

{

         if (a==b) return true;

        if (fabs(a-b)<absError ) return true;

        if (fabs(a>b) return  (fabs((a-b)/a>relError ) ? true : false;

       return  (fabs((a-b)/b>relError ) ? true : false;

}

這樣才相對完整。

 

而後這位大牛又給出了一個「最後的比較算法」:

(雖然文章較長,爲了避免改變原文的敘述,在這裏將其按照原文錄出)

咱們先看正數的狀況

根據IEEE的內存結構, 指數在高位,尾數在低位

浮點數大的對應的把其內存結構按照整數來理解進行比較的時候,狀況也是成立的

所以在這裏若是把他們進行比較的話,做爲整數運算效率會很是的高,好比

float f1 = 1.23;

float f2 = 1.24

f1 > f2 成立

(int&)f1 > (int&)f2 也是成立的

 

並且,仔細研究IEEE的浮點結構,能夠發如今《浮點數比較》當中提到的浮點數精度的問題——不是全部的浮點數均可以精確的表達

能夠精確表達的浮點數其實是有限的,就是那些IEEE的各類狀況的枚舉了 2^32個。不能表達的佔據了大多數

 

IEEE32位的狀況下,尾數是23位的(暗含了第一個位數是1

對於能夠精確表達的浮點數來講,若是咱們把這23位看成整數來理解, 它加1,就意味着能夠找到比當前對應浮點數大的最小的浮點數了

反之,咱們把兩個浮點數,對應的整數作差值運算,獲得的整數代表的是兩個浮點數之間有多少個實際能夠表達的浮點數(對應的指數相同的狀況下很好理解;指數不一樣的時候,也是一樣有效的)

 

這樣,對於兩個正的浮點數,他們的大小比較就能夠用 (int&)f1 - (int&)f2 來進行比較了

差值的結果實際上就應該是相對偏差了

這個相對偏差,不等同於廣泛意義上的相對偏差

它所表達的是,兩個浮點數之間可能還有多少個能夠精確表達的浮點數

這樣經過指定這個閾值來控制兩個浮點數的比較就更有效了

對於兩個正的浮點數

bool IsEqual(float f1, float f2, int absDelta)

{

     if ( abs ( (int&)f1 - (int&)f2 ) < absDelta ) return true;

}

這裏用abs而不是fabs這在asm上面的運算差距也是很大的了

 

對於兩個負數進行比較的狀況也是相同的

只不過負數內存對應的整數加1,相應的找到的是更小的負數而已

 

可是負數和整數之間如今還不能進行直接的比較,由於根據IEEE的內存結構

正數和負數是不一樣的,對應的整數不能連續

正的最小的數就是0了,對應的整數也是0x00000000

負的最小的數就是-0,對應的整數則是0x 80000000

不用奇怪-0

IEEE的表達當中是有兩個0的,一個是 +0  一個是-0

 

有趣的是,按照 f1 == f2 的判斷  +0-0是相等的


經過對比咱們能夠發現,

+0 和正的浮點數能夠按照轉換成爲整數的方式直接進行比較

-0 和負的浮點數能夠按照轉換成爲整數的方式直接進行比較

 

若是咱們可以把他們鏈接起來,整個整數方式的直接比較就完備了

對比一下負數的結構, 能夠找到一個簡單的辦法了:

        把負數內存對應的整數減去 -0 ,他們就連續了

並且更好的結果是,全部的負數通過此次減法後,對應的整數也都是負數了

這樣整個整數比較就變得連續了,並且在整個浮點數範圍內都是有效的了

 

最後的比較算法就是:

//  函數:   bool IsEqual(float f1, float f2, int absDelta)

//  功能:把比較兩個浮點數是否近似相同

//  輸入:f1, f2參與比較的兩個浮點數

//               absDelta 兩個浮點數之間容許有多少個其餘能夠精確表達的浮點數存在,至關於相對偏差

//  輸出:   true,兩個浮點數進行相等; false 兩個浮點數不等

//  注意:僅僅適合IEEE 32位浮點數結構

bool IsEqual(float f1, float f2, int absDelta)

{

       int i1, i2;

       i1 = ( f1>0)  ? ((int&)f1)  : ( (int&) f1 - 0x80000000 );

       i2 = (f2>0)  ? ((int&)f2)  : ( (int&) f2 - 0x80000000 );

       return   ((abs(i1-i2))<absDelta) ? true : false;

}

 

這位大牛的講解我暫時尚未弄懂,暫且先錄在這裏,之後在慢慢讀。別人看是,也清晰明瞭。
相關文章
相關標籤/搜索