看板 C_and_CPP 關於我們 聯絡資訊
有另外一個觀念其實對於理解浮點數運算也很有用: 浮點數其實就是有限有效位數的二進位科學記號 既然是科學記號那一些關於其運算的觀念換個底就能套用在浮點數身上 ※ 引述《lovejomi (JOMI)》之銘言: : 我理解為什麼float會有誤差值 : 但是今天朋友討論一件事情 : if (float_var == 1.0f) 這樣寫到底有什麼錯(我認知是 這樣寫 變數的值要完全跟1.0 : 四個byte的memcmp要一樣) : 1. 在誤差範圍內 (https://en.cppreference.com/w/cpp/types/numeric_limits/epsilo : n) : 如果是趨近於1的數字 我這樣判斷會失敗 導致邏輯錯誤? 所以因為這樣條件太嚴苛 : 對於經過運算後的float數值 很可能有一點點誤差產生就不成立了? : 2. 如果是要完全的相等 , 我能把一個float 一個byte一個byte判斷是否相等來判斷是不 : 是等值嗎? : 例如 : typedef union : { : float value; : unsigned char bytes[4]; : } IEEE754; : IEEE754 one; : one.value = 1.0f; : IEEE754 target; : target.value = input; : 然後memcmp 兩者的bytes : 還是 float 的== 實作上就是byte compare? : 3. 浮點數運算出現誤差,可以理解成 當除不盡 或是 除完小數點超過二進位小數 23位 : 無法表示 : 就會產生誤差? : 4. 因為看不懂std::numeric_limits<T>::epsilon 的那個almost_equal在幹嘛 所以找了 : 一下 : https://stackoverflow.com/a/17341/588477 : 這篇的方法好像是有道理但是請看以下測試 : https://ideone.com/MH6jJW : 我看VC直接寫 : #define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0+FLT : _EPSILON != 1.0 : GCC我用gcc -E -dM 去dump (我不知道為什麼找不到定義???怎麼解釋 https://tinyurl. : com/y8heekq8 ) : #define __FLT_EPSILON__ 1.19209289550781250000e-7F : 奇怪為什麼會是這樣 : a. stackoverflow的作法錯了? : b. 為什麼會把差值當成相等? : c. 到底這個epsilon 最應該用在哪裡呢? : d. 是不是把almost_equal當成一個正解 才是正確的浮點數比較相等呢? : 我用以下tool 把 epsilon 看他hex form 反推一下 : 他是2^-23 = 0.00000011920928955078125f乍看之下跟gcc定義一致 : https://www.h-schmidt.net/FloatConverter/IEEE754.html : 觀念上有些錯誤 : 請大家修正一下 : 謝謝 我們知道在用有限位數的科學記號表示實際值時會進行四捨五入 所以當使用這些經過處理的值運算就有可能產生誤差 十進位科學記號的對應例子例如: 1/3 可能會表示成 3.33333*10^-1 所以當三個這個值相加時會得到的是 9.99999*10^-1, 而不是 1.00000*10^0 為了這個原因我們通常會取一個小範圍, 兩者相減在誤差範圍內的話就視作相等 例如若想要差在 1*10^-4 以內當作相等的話, 上面兩個結果就會當作相等了 到這裡回答了你的 (1) 和 (3) (2) 其實所有數值型態的 == 都是 byte exact compate 所以才會說直接用 == 比的結果可能不是你要的 (4) 這裡則是又一個科學記號的名詞出現了: ulp ulp 全名 units in the last place, 直譯叫做「最後一位的單位」 其實指的就是這個科學記號的表示法當中最後一個有效位數上有 1 的數值 以上面我舉例的十進位六位有效數字來說 1.00000*10^0 的最後一個有效位數其值為 1*10^-5, 這就是這個浮點數的 ulp 這個單位通常用來表示誤差大小, 例如上面 1/3 + 1/3 + 1/3 的計算誤差就是 1 ulp 會拿這個當誤差大小的理由看科學記號的表示法很容易理解 那這裡的 epsilon 值其實就是代表某種型態的 1 的 ulp 大小 (這個 epsilon 的定義有個講法是最小的值使其加上 1 之後不等於 1 仔細想想這其實就是在說這個 epsilon 是表示 1 這個浮點數的 ulp 大小) 因此 cppreference 裡的範例就是在說 「x-y 的值 (兩者的誤差) 要小於給定參數個 ulp」 之所以要乘以 x+y 是為了要求得所求數值範圍裡的 ulp 實際值 因為很容易理解 1.00000*10^0 和 4.20000*10^1 和 5.77216*10^-1 的 ulp 都不同 實際上我們需要的 ulp 是多少可以用該值乘上 epsilon 來估計 (參照上一篇回答, 這個 ulp 間隔其實就是上一篇裡提的那些離散格子點) 那麼回到你的問題, 是不是一定要比較浮點數都要用它的 almost_equal 實作? 答案其實是要看你的用途而定 如果你的用途會需要這種類型的誤差估計, 那就需要 它會保證你的數字之間的誤差不超過某個數學上可以掌握的大小 如果只是平常的計算的話, 那其實可以不用這麼嚴格 像我最上面那樣抓一個大致足夠的小值, 相差不到這裡就當它相等就好 這個足夠的小值可以參照這些 epsilon 的實際值來訂 例如 std::numeric_limits<float>::epsilon (FLT_EPSILON) 其值是 2^-23 約是 1.192093 * 10^-7 所以對於 float 可以簡單取一個 10^-5 (寫 1e-5f) 當做平常在用的誤差值 取 100 倍左右的範圍可以適用到稍微大範圍一點的數字 而對於現在比較常用的 double std::numeric_limits<double>::epsilon (DBL_EPSILON) 其值是 2^-52 約是 2.220446*10^-16, 所以很常看到取 1e-10 ~ 1e-13 來用的寫法 -- 有人喜歡邊玩遊戲上逼; 也有人喜歡邊聽歌打字。 但是,我有個請求, 選字的時候請專心好嗎? -- 改編自「古 火田 任三郎」之開場白 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 123.195.192.32 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1537315494.A.002.html
cutekid: 大推(Y) 09/19 09:55
lovejomi: 謝謝講解, 有更多領悟 不過還是需要思考一下 09/25 17:18