距上一次寫原碼,反碼,補碼雜談已經有一段日子了。關於浮點數,我也有一些本身的思考,想寫出來,可是日月如梭,人懶如dog呀。所幸有一個週末,白無聊懶,我就開始動筆了。php
根據IEEE754 表示法,一個數能夠表示爲 :html
好了,從《深刻理解計算機系統》中把這段抄了下來,本文終..........bash
浮點數 (floating point number) ,顧名思義,小數點(point)浮動表示。 從上面咱們能夠看出浮點數的表示法,比原來咱們表示整數的時候直接把一個十進制數表示爲二進制數,看上去複雜多了。 那麼,咱們爲何須要浮點數表示法呢?函數
我我的的 觀點是ui
使用浮點數表示法 是爲了解決 在有限位數裏,能表示的小數太少 。並不是 爲了解決 小數在計算機中沒法精確存儲。spa
其實浮點數表示法 並不複雜。我以爲它的想法和高中數學中的一個函數有一種千絲萬縷的關係。.net
計算機的存儲方式是經過二進制存儲的。若是按照整數的存儲方式,咱們要表示小數也一樣要先將十進制的小數轉化爲二進制表示法。 例如:設計
二進制 | 分數 | 十進制 |
---|---|---|
0.0 | 0/2 | 0.0 |
0.1 | 1/2 | 0.5 |
0.01 | 1/4 | 0.25 |
0.001 | 1/8 | 0.125 |
0.0001 | 1/16 | 0.0625 |
0.00001 | 1/32 | 0.03125 |
結果咱們發現其實大部分的小數,由於沒法被2整除,而沒法用二進制精確表示。 其實從嚴格意義來講,二進制表示法也不能表示全部的整數,由於位數有限。那麼咱們如何 表示更大的數呢?擴增位數。 那麼對於二進制小數,咱們也能夠借鑑這種想法,擴增位數,表示更多的小數,對於某些沒法精確的小數老是能夠在有限範圍內經過舍入來表示。3d
可實際上,咱們若是把擴展位數x和所能帶來的表示範圍y,畫成圖像。 從圖像上看 其實 就有點像高中的冪指數函數 :code
(可是x的取值是離散的,只能取整數,y的取值也是離散的。因此圖像不是連續的。可是找個圖不容易,暫且將就着看吧。)從圖中來看 :
二進制 | 分數 | 十進制 |
---|---|---|
0.0001 | 1/16 | 0.0625 |
0.00001 | 1/32 | 0.03125 |
二進制擴增了一位,可是對應的十進制精度從0.0625-> 0.03125實際上最小位上的權仍是10^(-2) 並無移動。
這就是定點數二進制表示法在小數部分應用遇到的困境。怎麼辦?
m * 10^n
這種形式,而浮點數表示爲 M * 2^E
那麼根據IEEI754標準的,32位浮點數是如何表示的呢?這裏我借用了阮一峯老師畫的圖。他寫的浮點數介紹的文章講得很簡潔明瞭,一目瞭然。特地推薦一下:浮點數的二進制表示
由上圖:
exp!=0 && exp!=255
M = 1+f 當exp=0
時 , M=f有點繞,直接上實錘吧。 根據
上圖的二進制數表示的浮點數爲:
f : 0.01(2) = 0.25(10)
e : 01111100(2) = 124(10)
V = (-1)^S*M*2^E
= (-1)^0 * (1+ 0.25) * 2^(124-127)
= 1* 1.25*2^(-3)
= 1 * 1.25 * 0.125
= 0.15625
複製代碼
再舉個栗子:3.0625的浮點數二進制表示法是
第一步:
3.0625(10) = 11.0001(2)
第二步 : 小數點移位
11.0001 = 1.10001(2)* 2^(-1)(10)
第三步 : 根據(-1)^S*M*2^E 寫出s ,exp ,f
s = 0
f = 10001
exp = E+bias = -1+127 = 126 = 01111110
則3.0625 的浮點表示法爲
0 01111110 10001 000000 000000 000000
複製代碼
咱們能夠看到s和frace 老是比較容易理解的,就是E = e - bias 或 E = 1-bias這個會比較難以理解。 這個我我的的見解是 :
用浮點數表示的意義在於,能在有限的位數裏表示更多的數。
E的值若是隻是負數的話,或只是正數的話。就只能表示小數或只能表示整數。階碼的正負值使浮點數的表示能同時覆蓋到整數和小數。 那麼,爲何E 的表達方式不和咱們補碼的那種模運算表示法同樣呢?這個我也還沒想明白。 可是,對於這個疑問?《深刻理解計算機系統》第二章裏的介紹關於 規格化數 和 非規格化數這幾個概念時 有提過這樣設計的緣由 : (1) 是爲了將非規劃化數和平滑過渡到規格化數 (2) 是爲了浮點數能使用整數排序函數來進行排序
這樣能夠保證50%的狀況是向上舍入,50%是向下舍入,不會在計算這些數的平均值時帶入誤差。
好吧,仍是《深刻理解計算機系統》第二章說的,它有詳細地介紹。可是我已經放棄了。哈哈哈.......
因爲浮點數的非精確存儲,因此兩個浮點數不能直接比較相等。
未必。這裏,請容許我插入一張圖-----輪子哥在知乎的回答。
因爲浮點數的非精確存儲,帶來的問題一般是在計算時引發的。而且實際上計算的順序也會影響丟失的精度範圍。而對於未通過計算的,總不可能上一行代碼中的二進制表示法和下一行的就不一致了。他就算不精確存儲也應該有同一種相同的舍入策略(指存儲時的舍入策略,而非計算時的截斷策略)。因此,對於未通過計算的數值大可放心地直接進行數值比較。 常見場景 : 用戶輸入一個小數,判斷是否小於等於一個常量(小數)。這種能夠放心地使用 <=
那麼若是對於通過計算的浮點數,要如何比較是否相等呢? 上一段熟悉的代碼,大一在機房看到比較兩個數是否相等要取絕對值 < 0.00001 一臉懵逼.gif 影響深入。
#define EPSILON 0.000001 //根據精度須要
if ( fabs( fa - fb) < EPSILON )
{
printf("fa<fb\n");
}
複製代碼
爲什麼要取0.00001? 好吧,我也在找這個答案。
bccomp()
函數進行比較。 bccomp()
實際上是BCMath
提供的一個函數。若是你須要更精確地進行浮點數的運算,都在BCMath
中能夠找到對應的函數。值得一提的還有這段:
2^ (-16) = 0.0000152587890625 => 接近於精度 0.00001
複製代碼
這會不會就是浮點數比較大小的epsilon
要取0.00001
的由來?
第一次發現這個是由於大三學單片機,調C代碼的下溢bug。而後就很好奇在php中若是int類型的數是否有溢出的問題。用上面的代碼測了一下,發現結果並無,這個具體是怎麼作到的,我也很好奇。就先把坑挖在這裏留待之後解決吧。
以上就是我對浮點數的 一些思考。因爲做者見識有限,文中不免紕漏繁多。歡迎讀者交流指正。
參考閱讀: