浮點數雜想

距上一次寫原碼,反碼,補碼雜談已經有一段日子了。關於浮點數,我也有一些本身的思考,想寫出來,可是日月如梭,人懶如dog呀。所幸有一個週末,白無聊懶,我就開始動筆了。php

IEEE754 浮點表示法

根據IEEE754 表示法,一個數能夠表示爲 :html

V= (-1)^S*M*2^E
  • 符號 s(sign) s決定這個數是負數仍是正數
  • 尾數 M M是一個二進制小數
  • 階碼 E(exponent) E 的做用是對浮點進行加權,這個權重加權,這個權重是2的E次冪(多是負數)

好了,從《深刻理解計算機系統》中把這段抄了下來,本文終..........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

2^x 冪指數函數圖
(可是x的取值是離散的,只能取整數,y的取值也是離散的。因此圖像不是連續的。可是找個圖不容易,暫且將就着看吧。)

從圖中來看 :

  • 整數 對於整數來講,他對應的是2^x函數的第一象限。在該象限裏,隨着x也就是位數增長,可表示的範圍y呈指數型增加。
  • 小數 可是對於小數來講,對應的是2^x第二象限。在該象限裏,隨着位數的增長,可表示的範圍y增加趨於平緩。也就是說位數擴增對於可表示的浮點數精度,效果並不明顯。
  • 結論 對應的大白話來講就是:可能對於底層硬件擴增了n位,可是所能表示的十進制數可能最小精度值一位都沒移動。 典型地,你看:
二進制 分數 十進制
0.0001 1/16 0.0625
0.00001 1/32 0.03125

二進制擴增了一位,可是對應的十進制精度從0.0625-> 0.03125實際上最小位上的權仍是10^(-2) 並無移動。

這就是定點數二進制表示法在小數部分應用遇到的困境。怎麼辦?

  • think 若是咱們能把函數圖像往 右邊移動一下。讓小數的表示能落在第一象限。那麼對於同等位數的機器,增大 X 所能表示的小數範圍 Y 就會相對更大一些,也就是最小精度能更精細一些。 其實 想到這裏的話,我就以爲用上浮點數表示法就很天然了,並非刻意的。很水到渠成。向右移動這個2^x 函數, 獲得的函數圖像對應的就是2^(x-E) 函數。其中E這個偏移值 就對應了浮點數中,小數點移動的位數,也對應了浮點數表示法中的階碼(exponent)。 其實這也有點 科學計數法的感受。科學計數法是把n個0,寫成m * 10^n這種形式,而浮點數表示爲 M * 2^E

32位浮點數的二進制表示法

那麼根據IEEI754標準的,32位浮點數是如何表示的呢?這裏我借用了阮一峯老師畫的圖。他寫的浮點數介紹的文章講得很簡潔明瞭,一目瞭然。特地推薦一下:浮點數的二進制表示

32位浮點數表示法

由上圖:

  • sign 符號位,用來區分正負
  • exponent 這個其實應該寫做exp纔對,真正的exponent 應該指得是 E 階碼。 而且 E = e-bias 或 E = 1-bias(當exp全爲0 時)。其中bias定義爲等於(2^k-1)的值,也叫作偏置值。
  • frac 就是尾數,也叫位域了。 當exp!=0 && exp!=255 M = 1+f 當exp=0時 , M=f

有點繞,直接上實錘吧。 根據

V= (-1)^S*M*2^E

上圖的二進制數表示的浮點數爲:

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) 是爲了浮點數能使用整數排序函數來進行排序

  • 舍入 咱們 還能夠看到32位的浮點數,frace的位數是23,那麼若是一個小數表示爲二進制數,一直除不盡,浮點移位後 frace > 23呢?怎麼辦? 舍入。根據IEEE754的標準是向最接近的能精確表示的小數舍入,若是最精確的有兩個,就向最低位是偶數舍入。

這樣能夠保證50%的狀況是向上舍入,50%是向下舍入,不會在計算這些數的平均值時帶入誤差。

好吧,仍是《深刻理解計算機系統》第二章說的,它有詳細地介紹。可是我已經放棄了。哈哈哈.......

浮點數不能直接比較相等

因爲浮點數的非精確存儲,因此兩個浮點數不能直接比較相等。

未必。這裏,請容許我插入一張圖-----輪子哥在知乎的回答。

浮點數不能直接用==比較?

因爲浮點數的非精確存儲,帶來的問題一般是在計算時引發的。而且實際上計算的順序也會影響丟失的精度範圍。而對於未通過計算的,總不可能上一行代碼中的二進制表示法和下一行的就不一致了。他就算不精確存儲也應該有同一種相同的舍入策略(指存儲時的舍入策略,而非計算時的截斷策略)。因此,對於未通過計算的數值大可放心地直接進行數值比較。 常見場景 : 用戶輸入一個小數,判斷是否小於等於一個常量(小數)。這種能夠放心地使用 <=

那麼若是對於通過計算的浮點數,要如何比較是否相等呢? 上一段熟悉的代碼,大一在機房看到比較兩個數是否相等要取絕對值 < 0.00001 一臉懵逼.gif 影響深入。

#define EPSILON 0.000001 //根據精度須要
    if ( fabs( fa - fb) < EPSILON )
    {
            printf("fa<fb\n");
    }
複製代碼

爲什麼要取0.00001? 好吧,我也在找這個答案。

浮點數與PHP

  • 在PHP中如何比較浮點數 關於浮點數的比較PHP手冊也有介紹
    浮點數比較大小
    在PHP中,若是須要比較浮點數的大小,可使用bccomp()函數進行比較。 bccomp()實際上是BCMath提供的一個函數。若是你須要更精確地進行浮點數的運算,都在BCMath中能夠找到對應的函數。

值得一提的還有這段:

浮點數的精度

2^ (-16) = 0.0000152587890625   => 接近於精度  0.00001
複製代碼

這會不會就是浮點數比較大小的epsilon 要取0.00001的由來?

  • PHP中沒有整型溢出,浮點數也不會溢出。 與傳統的強類型語言不一樣,php中並不會出現整型溢出現象。當一個int型的數溢出時會自動轉爲float類型,當一個float類型的數溢出時會維持float所能表達的最大值。
    溢出

第一次發現這個是由於大三學單片機,調C代碼的下溢bug。而後就很好奇在php中若是int類型的數是否有溢出的問題。用上面的代碼測了一下,發現結果並無,這個具體是怎麼作到的,我也很好奇。就先把坑挖在這裏留待之後解決吧。


2018-10-01 新增長 php中int類型和float類型溢出時,自動轉化這個問題。鳥哥的博客有介紹。 詳見 : 關於PHP浮點數你應該知道的(All 'bogus' about the float in PHP)

以上就是我對浮點數的 一些思考。因爲做者見識有限,文中不免紕漏繁多。歡迎讀者交流指正。


參考閱讀:

相關文章
相關標籤/搜索