作者Favonia (青峰好帥)
看板b97902HW
標題Re: [計程] 你不可不知道的 double 十件事
時間Thu Oct 2 21:20:43 2008
有一些容易產生誤解的地方來解釋一下 xD
※ 引述《iForests (森林)》之銘言:
: ‧浮點數誤差‧
: 用到 double 時是無論如何都得提到浮點數誤差的,不過這裡只打算亂講。簡單來說
: 是這樣,當你放例如 5 在變數裡時,電腦有可能存 4.9999999999999999999999999999。
: 原則上是沒有差別,但有時候會造成影響。使徒三是個好例子,很多人寫一寫可能會發現
: 印出來的東西竟然有 -0.000,其原因正是因為變數值為像 -0.00000000000002147483647
: 這種後面接了一些怪東西的值(只是隨便打的)。因此輸出時認為它不是 0,而是一個很
: 小很小的負數,所以取 3 位小數時印出 -0.000。
這裡可能會誤導人。雖然原因應該是有一個很小的負數,
但是真的有 -0 這個東西。浮點數格式有 -0 和 +0 :)
我剛才發現 Wiki 還有介紹 xD
http://en.wikipedia.org/wiki/%E2%88%920_(number)
: 解法方法很簡單,我們加一個很小很小又不至於太小的數給它,例如 1e-10。
: -0.00000000000002147483647
: +0.0000000001
: ------------------------------
: 0.00000000009997852516353
: 這樣一來,用 %.3lf 印出時,答案就會是 0.000 了。
這地方有一點小問題,請看下面。
: ‧數學函式‧ (使用前需先 #include <math.h>)
: 首先,π = 2.0 * acos(0.0) = 3.1415926535897932384626433832795。
: ↑這是確確實實的 0.0 而不是表情符號
: sin()、cos() 等等函數傳入的參數必須使用弳度而非角度,轉換方法高中數學課應
: 有提過,總之:弳度 = 角度 * π / 180。
這邊似乎有點太理想了 xDDDD
上面這一行丟到編譯器去執行
得出來的結果應該是
3.141592653589793
115997963468544185161590576171875... (double)
而不是
3.141592653589793
2384626433832795... (真的圓周率)
對了,也可以直接用 acos (-1) 喔!
: 推 LoganChien:#include <float.h> 之後,你可以用 DBL_MAX 代表極大 09/30 22:40
: → LoganChien:的值(double 可以表示的最大值) 09/30 22:40
: 推 LoganChien:檢查二個 double 是否相同,最好用下面的公式,以減少 09/30 22:48
: → LoganChien:意外。﹝誤差造成的﹞ 09/30 22:48
: → LoganChien:fabs(a - b) < DOUBLE_PRECISION_EPSILON 09/30 22:49
: → LoganChien:let DOUBLE_PRECISION_EPSILON = 1e-9. 09/30 22:49
以上是用絕對誤差來實作,通常也都不會有什麼問題,
不過如果更深入了解 double 的格式的話,
就會知道 double 的誤差是
相對的而不是絕對的。
意思是說,對於 1000000000 來說 1e-9 的誤差比較容易發生,
但對於 0.000001 來說 1e-9 就比較不容易了。
(可參考
http://en.wikipedia.org/wiki/IEEE_754-2008 或是計概應該會教)
事實上「浮點數」在字面上就講明小數點是「浮動」的
例如,一般我們寫成 5000 和 0.0005 的兩個數,
就實際上在記憶體中浮點數的儲存方法來看,
應該理解成 5 * 10^3 和 5 * 10^-4 比較恰當
所以誤差是相對的而不是絕對的
(附註:電腦裡面用的是二進位,所以更接近 x * 2^y)
那到底要怎麼去處理浮點數的誤差?
這是個很複雜的問題,端看你的目的而定。
以下這篇文章有講如何比較兩個數字是否一樣:
http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
另外我在這邊提供一個思考的觀點,
到底要允許計算中有多大的誤差可以用最後的要求來定,
例如這次的作業可能要求小數點以下三位,
如果你估計最後的答案是 1000 附近(我只是舉例)
那麼你可能就要讓你整個運算的
相對誤差控制在 0.0000005 以內 (0.5e-6)
或是
絕對誤差控制在 0.0005 以內 (0.5e-3)
這樣子最後印出來時才會和正確(沒有誤差)的答案比較接近。
運算中的誤差傳遞我相信普物應該有教 xDDD
大致上來說,加法比較不會讓誤差擴大太快 lol
該講的講完了,至於要怎麼寫,套一句劉墉的話,「你自己決定吧!」
: → LoganChien:1. 單純的習慣,我也有看過有人用 1e-8, 1e-10 10/01 00:11
: → LoganChien:2. (印象中) C++ 標準規定 double 最低精確度是 1e-9 10/01 00:12
我相信到這邊 1 應該比較清楚了
至於 2 又要花篇幅講了~
由於大家學的是 C,加上我沒有看過最新 C++0x 的標準
所以在這裡我只講 C99 的狀況
C99 的標準後面的 Annex F 有講說
C 裡面的 float, double, long double
和 IEC 60559:1989 (也就是 ANSI/IEEE 754-1985) 如何對應
簡單來說,要研究 C 的 double 是怎麼運算的,可以查閱上述標準的
倍精浮點數(double-precision floating point number)
先一下歷史和題外話 xD
其實除了 IEEE 754-1985 以外
還有一個 IEEE 854 的標準也在規定浮點數的運算
啊不過最近 IEEE 754-2008 (又稱 IEEE 754r... 名稱超多 - -|||)
把這兩個舊標準都吃掉了
ANSI/IEEE 754-xxxx 和 ISO/IEC 9899:1999 (C99 的標準)
都是要$$$$$的
還好在官網上面可以下載定案前的 "draft" (非正式版,但是跟正式版很接近這樣)
而且也有很多依照這些標準寫的教學網站,好讀好懂又免費!
好,那這些標準到底怎麼規定誤差的呢?
就像之前講的,這些標準有訂出 double 在記憶體上應該怎麼儲存,
格式會接近這樣:
(有一些例外的狀況請自己看文章:http://en.wikipedia.org/wiki/IEEE_floating-point_standard )
S 1.
XXXXXXXXX * 2^
YYYYYYY
例如
+ 1.
00010100 * 2^(
+001011)
每個亮色的地方都要花記憶體儲存,而 double 短短 64 bits 就由這三部份所瓜分
S 是正負號(一個 bit 就夠了),
YYYYY 的長度決定後面的指數部份可以多大多小,
而
XXXXX 的長度決定了精確度,一切的罪孽都是來自
XXXXX 不夠長 -v-
double 的
YYYY 和
XXXX 都比 float 的
YYYY 和
XXXX 還多位,
所以不僅可以處理比較大的數字,數字也可以存比較多位(誤差小)。
double 的
XXXX 只有 52 位~
電腦要把運算結果塞回記憶體中時,很可能就會產生誤差!
預設的誤差處理方法是二進位的「四捨六入五取雙」
大致上是取最接近運算答案的表示方法~
講到這邊,聰明的大家應該就可以知道,
單純因為 double 儲存格式產生的相對誤差大約是 2^-5X (有些小細節我不想講了 orz)
大約是 10^-15 ~ 10^-16 左右
(也就是十進位下大約有 15 位 xD 大家可以看前面的圓周率大約第幾位開始爆炸)
至於 C++ 我猜應該也是用同樣的標準玩的
有興趣歡迎補充 xDDDD
--
我在 P2 也有版喔 xD 同 ID 喵~
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.30.39
※ 編輯: Favonia 來自: 140.112.30.39 (10/02 21:31)
1F:推 godgunman:是大彼得耶!!!! 10/02 21:41
2F:推 godgunman:話說, 今天助教時間有講IEEE-754 10/02 21:47
※ 編輯: Favonia 來自: 140.112.30.39 (10/02 21:52)
3F:推 wolfdigit: 大彼得好威!!! 10/02 21:54
4F:推 ming1053: 大彼得好威!!! 這篇講的好清楚 10/02 22:04
5F:推 ckclark:-0今天忘了講 10/02 22:17
6F:推 iForests:有神快拜!!!!!(拜) 10/02 22:17
7F:推 scan33scan33: 大彼得好威!!!(拜) 10/02 22:46
8F:推 anfranion:傳說中的大彼得(仰望) 10/02 22:53
9F:推 rewqrewwq:感謝學長指教!(拜) 10/02 23:03
10F:推 Bakaking:娘子,快跟牛魔王出來見上帝! 10/02 23:53
11F:→ benck:推一個 字好多喔 要慢慢消化 10/03 00:07
12F:推 sa072686:有神快拜!!!!!(拜) 10/03 00:22
13F:推 LoganChien:拿香跟拜!(拜) 10/03 00:23
14F:→ LoganChien:獲益良多! 10/03 00:25
※ 編輯: Favonia 來自: 140.112.30.39 (10/03 02:07)
15F:推 pigalan: 大彼得好威!!!(拜) 10/03 22:24