Re: [問題] float 和 double 的問題

拋磚引玉。引文做 high-light。 可能要講的太多了,也不知道你對於 IEEE754 了解到哪種程度,所以大多人也不知該從 何講起,唯這問題似乎真的卡很久,只丟一點小弟知道的東西討論。 但是我看有些網頁用IEEE745的表示法下去算.. 範圍卻是1.2E-38~3.4E+38 不知道那個是正確的呢?? 這個是正確的,詳細的推導在中文的 wiki 介紹就很清楚了,但尷尬的是浮點數表示法 不一定從 IEEE754 規格,即使從 IEEE754 規格,不同編譯器細節上的操作也可能會有 所不同,如果我沒記錯的話,gcc 3.4.1 (就是你 dev-c++ 用的 gcc 版本) 和 vc 在 運算上,及最小值定義上就有所不同。故一般書籍都是建議去查 float.h 之 macro。 float a=0.01; 首先 a 存 0.01 時,在存入 ieee 754 就會有誤差了,單純轉成二進位約是 0.000000 101000111101011100001010 0011110101 .... 正規化後得 1.01000111101011100001010 * 2^-7 ,再存入 IEEE754 欄位中。 但將上面這數值再轉回十進位時,它只能存 23 位(加上小數點前隱性的1是24位), 這種實際上不只23位可表達的十進位,最後必然產生捨位誤差(Lemma),實際上得到 的就已經不是 0.01,捨位誤差後得到的約是 0.0099999997764826 Q1 : 可是書上寫的是「小數後7位」,你上面寫的已經到「16位了耶」 A1 : 所以說了,書上寫的都只是概估值,他們說的「小數點後七位」,算法是 2^(23+1) = 10^x , x = log10(2^24) = 24 * log10(2) = 7.22,但這數 據也只能當參考而已,我們光是拿最後一個 mantissa 位元 2^-23 這位來算, 它實際表示是 0.00000011920928955078125 ,是小數點後 23 位,不就超過小 數點後七位了嗎? 其中2^(23+1),裡面的 +1,正代表著正規化後,小數前唯一的一個1。 Q2 : 可是我用 printf 根本就看不出來它的效果耶? A2 : 原因是 printf 根本就沒在分 %f, %lf,在 printf 裡面,你拿 double / float 混用於 %f , %lf 結果都是一樣的,會先轉成 double,(強調是 printf ) 這種方式根本就看不出結果,所以關於 float/double 混用比較建議自己搞。 [Lemma] 「捨位誤差」有時候會看小數後第 24 位,像是十進位表示法的「四捨五入」 ,如果第24位是0就刪掉,如果第24位是1,前23位都加1。這種做調整的方式 不只一種,像無條件進1、無條件捨去等 (所以產生了四捨六入五成雙說法), 但這裡小提一下,就不再深入。 我還是覺得奇怪..如果浮點數存的時候就有誤差(因為只有23位可存尾數).. 所以存在電腦裡應該0.01是存成0.00999999.... 那在printf("%f",0.01)的時候..應該就會印成0.00999999 原因是預設列印是輸出小數後 6 位數進行四捨五入,然後答案是 0.009999999... 自然就變 0.01。 這樣乘下來怎麼去看它的結果精確度到幾位? 還有我參考了很多文章..都說乘法時並不是用float下去算是用double 儲存再放到float裡..又更複雜了.. 這個我記得有可能是這樣情況沒錯,這問題實質上相依於 compiler 較多,確實 有些 compiler 在 float 運算時會先偷轉 double 再放到 float,如 BCB 就這 樣,原因是對於現代 GPU 而言(扣除掉叫顯卡做這運算),double 除了精度高, 速度也比 float 還快,(所以一般 C++ 調用 double 系列之 math library , 有些情況下比調用 float 系列之 library 速度來得快),其他部份沒再特別確認。 3535=110111001111 float 0.01*3 =1.0100011110101110*2^-7 X 1.01*2^2 (小數點後2位) *3535= X 1.10111001111*2^11(小數點後11位) (0) a = 0.01 (dec) = 1.01000111101011100001010 * 2^-7 b = 3535 (dec) = 1.10111001111000000000000 * 2^11 (1) mantissa 先相乘 1.01000111101011100001010(bin) * 1.10111001111000000000000(bin) = 10.00110101100110011001100 1100010110000000000000 (bin) 上面這結果用手算會算到爆,建議直接先去找大數函式庫下來做 < 我是之前自己做過一份 binary 就是了 > (2) 指數相加 , -7 + 11 = 4 (3) 正規化, c = a * b = 10.00110101100110011001100 * 2^4 = 1.00011010110011001100110 * 2^5 這個數字轉到 10 進位約是 35.3499984741210940(誤差約1.5e-6)但誤差是從 0.01 開始就存在的,所以導致不如所預期的誤差在 1E-7 以下, 原因都是在於真正運算,必須考量實際是以二進位方式做運算,且會有捨位誤差 現象之存在。 再來是看這 sample。 int main() { float x = 0.01; float y = x * 3535; printf("%.16f\n", x * 3535); printf("%.16f\n", y); return 0; } 兩個答案都是 35.349984741120940,但如果你使用了 %.2f,或%.3f 時, 這裡的「顯示」會是小數後 2/3 位四捨五入,達成「看似無誤差」的 35.350, 但真正數值還是要以二進位方式,深入欄位格式做分析才準! 有人知道怎麼看乘法的精確度嗎?? 如果你期望的是,希望能正確在某次運算後,得到答案的 正確誤差值,那我只能說 很遺憾的,基本上辦不到,或很難!原因在於一般 user / coder 在填寫十進位字面 常量時,這時候就會有捨位誤差存在甚至有其他更多原因可導致誤差,但說完大概 也出了一本書了。而誤差是多少?不一定!要看轉到欄位裡才知道,有些是 10^0 級 ,有些是 2^-n 級, 更有些是 2^n 級,都不一定。 我沒記錯的話,之前自修看計算機組織與結構(白算盤),裡面有個章節都有討論 IEEE754 ,而且還蠻詳細的,包含 + - * / 都有流程圖出來,裡面的 K 完會蠻有幫助的。 --- 基本上這篇問題該回答的都答完了,如果有什麼其他不懂、疑惑的地方, 或許再以二進位方式做欄位分析,會得到更多答案。 其他比較 funny 的議題,像是小算盤按上 1/3 得到 0.33333333...,結果再 *3 , 為什麼可以再回到 1 之類的 (有些計算機只能到 0.9999999),這種議題就留下來做 思考,不再贅述。 -- ~ 這輩子與神手無緣 我只好當神獸了 ~ -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 180.177.76.161 ※ 編輯: EdisonX 來自: 180.177.76.161 (01/17 20:29)
hateexam:推 好文~ 01/18 00:38
sunlights:好厲害!!! 01/18 01:25
VictorTom:推; 另外以前也看過直接暫放浮點暫存器(80b), 比64b的 01/18 03:15
VictorTom:double精度更高; 以前被這種optimize造成test fail過Orz 01/18 03:16
EdisonX:V 大沒講我都忘了這件事,確實有種說法是若 long double 01/18 03:28
EdisonX:吃 80 bits,速度和精度會比 double 快,所以會用 80b 算, 01/18 03:29
EdisonX:不過一直沒機會驗證, 謝謝 V 大補充提點 :) 01/18 03:29
azureblaze:x86的fpu只做了80bit的,所以float和double都直接轉80 01/18 09:42
azureblaze:算完放回記憶體的時候再把多的位數砍掉 01/18 09:43
azureblaze:因為效率之類的問題很多人都不鳥IEEE 01/18 09:47
azureblaze:所以跨平台浮點運算要得到一樣的結果超麻煩 01/18 09:47
azureblaze:(做連線遊戲、replay之類的) 01/18 09:48
azureblaze:光是intel和amd結果就會不同了 01/18 09:51
EdisonX:原來如此,感謝 a 大補充。 01/18 14:38
amozartea:學到了..原來有時call double會比float快... 01/19 02:15
hilorrk:新版 QEMU 為了解決這問題把所有浮點運算用軟體模擬... 01/19 23:04
推文自動更新已關閉

留言

這個網誌中的熱門文章

[閒聊] 女生會觀察男生手上的手錶嗎?

[翻譯] Nosleep-一群變態鎖定觀看YouTube的孩童