海闊天空

          I'm on my way!
          隨筆 - 17, 文章 - 69, 評論 - 21, 引用 - 0
          數據加載中……

          浮點數比較

          在數學運算當中經常會涉及到判斷兩個數是否相等的情況
          對于整數很好處理 A==B這樣的一個語句就可以解決全部的問題
          但是對于浮點數是不同的

          首先,浮點數在計算機當中的二進制表達方式就決定了大多數浮點數都是無法精確的表達的
          現在的計算機大部分都是數字計算機,不是模擬機,數字機的離散化的數據表示方法自然無法精確表達大部分的數據量的。

          其次計算機浮點數的精度在單精度float類型下,只有7位,在進行浮點運算的時候,這個精度往往會導致運算的結果和實際期望的結果之間有誤差

          因為前兩個原因,我們很難用 A==B來判定兩個浮點數是否相同

          很自然,我們可以想到 fabs(A-B) < epsilon 這樣的一種判別方法
          但是這種判別方法穩妥嗎?
          它也不穩妥。

          首先, epsilon是一個絕對的數據,也就是誤差分析當中說說的絕對誤差
          使用一個固定的數值,對于float類型可以表達的整個數域來說是不可以的
          比如epsilon取值為0.0001,而a和b的數值大小也是0.0001附近的,那么顯然不合適
          另外對于a和b大小是10000這樣的數據的時候,它也不合適,因為10000和10001也可以認為是相等的呢
          適合它的情況只是a或者b在1或者0附近的時候

          既然絕對誤差不可以,那么自然的我們就會想到了相對誤差
          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溢出
          這個可以改造
          把除數選取為a和b當中絕對數值較大的即可
          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的標準,浮點數的定義如下

          符號位 指數位 小數部分 指數偏移量
          單精度浮點數 1 位[31] 8位 [30-23] 23位 [22-00] 127
          雙精度浮點數 1 位[63] 11 位[62-52] 52 位[51-00] 1023

          我們以單精度浮點數來說明:
          符號位,表述浮點數的正或者負
          指數實際也有正負的,但是沒有單獨的符號位,而是采用了一個偏移來表示
          在計算機的世界里,進位都是二進制的,指數表示的也是2的N次冪
          這個數據格式當中的,指數是8位,可表達的范圍是0到255
          而對應的實際的指數是-127到+128
          這里特殊說明,-127和+128這兩個數據在IEEE當中是保留的用作多種用途的
          -127表示的數字是0
          128和其他位數組合表示多種意義,最典型的就是NAN狀態

          小數部分,并不是一個浮點數的實際的小數
          實際的小數在這個小數前面還保留了一個1
          拿浮點數1.0來說
          符號位是0, 實際指數是0,對應這里的指數就是127了,也就是0x7f
          而小數部分就是1.0了, 1是暗含的不存儲,實際的小數部分就是0了
          因此組合起來的數據就是,0x3f80000

          可以用一個類來表示:
          class FloatType
          {
          public:
                union {
                   DWORD m_dwInt;
                   float          m_fFloat;
                 struct {  

                  int    m_nFra: 23;

                    int    m_nExp : 8;

                    bool m_bSign : 1;

                };
          };

          -----------------------------

          參考IEEE的浮點數格式說明
          對于0到1范圍內的浮點數是可以壓縮的

          顯然在0到1的范圍內,一個單精度的浮點數,指數和符號位占據9個bit
          而這9個bit是可以不用的,把它去除,只保留小數部分的23bit就可以達到壓縮的目的
          可以把一個浮點數從32bit,4字節壓縮到23bit,3字節的范圍內

          這也是在3dmax等一些工具軟件當中對浮點數進行壓縮存儲的方法。
          比如,在單位化的法向量當中,每個浮點數都是0,1范圍之間的數據
          正常情況下表示三維空間當中的單位化法向量就需要12個字節
          而經過這個壓縮處理,只需要9個字節

          -----------------------------


          在寫了上篇 浮點數的比較 以及 浮點數內存結構 兩篇文章后
          對于浮點數的比較有新的想法

          我們先看正數的情況
          根據IEEE的內存結構, 指數在高位,尾數在低位
          浮點數大的對應的把其內存結構按照整數來理解進行比較的時候,情況也是成立的
          因此在這里如果把他們進行比較的話,作為整數運算效率會非常的高,比如
          float f1 = 1.23;
          float f2 = 1.24
          f1 > f2 成立
          (int&)f1 > (int&)f2 也是成立的

          而且,仔細研究IEEE的浮點結構,可以發現在《浮點數比較》當中提到的浮點數精度的問題——不是所有的浮點數都可以精確的表達
          可以精確表達的浮點數實際上是有限的,就是那些IEEE的各種情況的枚舉了 2^32個。不能表達的占據了大多數

          IEEE在32位的情況下,尾數是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;
          }

          posted on 2009-07-05 21:52 石頭@ 閱讀(4081) 評論(0)  編輯  收藏 所屬分類: java_base

          主站蜘蛛池模板: 交城县| 河东区| 武宁县| 科技| 怀来县| 洱源县| 青州市| 岚皋县| 新绛县| 武威市| 江源县| 子洲县| 镇坪县| 定西市| 古浪县| 台中市| 枣强县| 察哈| 叙永县| 永胜县| 湖州市| 扬州市| 高尔夫| 高陵县| 象州县| 文安县| 靖州| 乐清市| 大方县| 成安县| 古蔺县| 天镇县| 永春县| 旬邑县| 三河市| 太原市| 喀喇沁旗| 株洲市| 丹寨县| 昌吉市| 新宾|