Float的基本介紹

關於浮點數,爲何它生來就可能存在偏差?帶着好奇查閱了一些介紹,而後作了簡單彙總。這只是一段知識的開始,後續還會繼續完善。(很難過,這裏的MarkDown不支持編輯公式。請移駕本人的博客地址: http://neojos.com )

—— 蕩蕩上帝,下民之闢。疾威上帝,其命多闢。天生烝民,其命匪諶。靡不有初,鮮克有終。git

Floating-point represent

浮點數在計算機中是如何表示的?由於它有小數點,那麼小數點後面的數,該如何用二進制來表示呢?咱們都知道,浮點數自己就存在偏差,在工做中,你所使用的float都是一個近似數。這又是什麼緣由致使的呢?golang

1. Fixed-point

fixed-point 是將bit位拆分紅固定的兩部分:小數點前的部分和小數點後的部分。拿32 bitfixed-point表示舉例,能夠將其中的24 bit用於表示整數部分,剩餘的8 bit表示小數部分。less

假如要表示1.625,咱們能夠將小數點後面的第一個bit表示$\frac12$,第二個bit表示1/4,第三個1/8一直到最後一個1/256。最後的表示就是00000000 00000000 00000001 10100000。這樣其實也好理解,由於小數點前是從$2^0$開始向左成倍遞增,小數點後從$2^{-1}$開始向右遞減。socket

由於小數點後面的部分始終小於1,上面這種表達方式能表達的最大數是255/256。再比這個數小,這種結構就沒法表示了。函數

Floating-point basics

根據上面的思路,咱們用二進制表達一下5.5這個十進制數,轉化後是$101.1_{(2)}$。繼續轉換成二進制科學計數法的形式:$1.011_{(2)} * 2^2$。在轉換的二進制科學計數法過程當中,咱們將小數點向左移了2位。就跟轉換十進制的效果同樣:$101.1_{(10)}$ 的科學計數形式爲$1.011 * 10^2$oop

對於二進制科學計數法表達的5.5,咱們將其拆分紅2部分,1.011是一部分,咱們稱爲mantissa。指數2是另外一部分,稱爲exponent。下面咱們要將$1.011_{(2)} * 2^2$ 映射到計算機存儲的8 bit結構上。spa

咱們用第一個bit來表示正負符號,1表示負數,0表示正數。緊接着的4 bit用來表示exponent + 7後的值。 4 bit最大能夠表示到15,這也就意味着當前的exponent不能超過8,不能低於-7。最後的3 bit用於存儲mantissa的小數部分。你可能有疑問,它的整數部分怎麼辦呢?這裏咱們約定整數部分都調整成1,這樣就能夠節省1 bit了。舉個例子,若是要表示的十進制數是0.5,那麼最後的二進制數不是$0.1_{(2)}$,而是$1.0 * 2^{-1}$。最後表示的結果就是:0 1001 011code

再來一個decode的例子,即將0 0101 100還原回原始值。根據以前的描述0101表示的十進制是5,因此exponent = -2,表示回二進制科學計數法的結果:$1.100 * 2^{-2} = 0.011_{(2)}$。咱們繼續轉換成真實精度的數:0.375orm

最後能夠看在,若是mantissa的長度超過3 bit表示的範圍,那麼數據的存儲就會丟失精度,結果就是一個近似值了。blog

1. Representable numbers

繼續按照上面的思路,如今8 bit的浮點表示能表示的數值區間更大。

要表示最小正數的話,sign置爲0,接下來的4 bits置爲0000,最後的mantissa也置爲000。那麼最終的表示結果就是:$1.000_{(2)} * 2^{-7} = 2^{-7} ≈ 0.0079_{(10)}$

表示最大正數的話,sign置爲0,其餘位也都置爲1。最終表示的結果:$1.111_{(2)} * 2^{8} = 111100000_{(2)} = 480_{(10)}$。因此8 bits浮點表示的正數範圍(0.0079, 480]。而8 bits 二進制表示的範圍是[1, 127]。範圍確實大了不少。

可是必須注意:浮點數沒法準確表示該區間內的全部數。拿十進制51來講,用二進制表示是110011。轉化爲8 bits的浮點數表示:$110011_{(2)} = 1.10011_{(2)}*2^{5}$。當咱們試着去存儲的時候,發現3 bitsmantissa放不下如今的10011。咱們不得不作近似取值,將結果修改成$1.101_{(2)} * 2^{5} = 110100_{(2)} = 52_{(10)}$。因此,在咱們8 bits 表達的浮點數中51 = 52。這樣的處理有時候讓咱們很無奈,但這也是爲了讓8 bits表示更大範圍的數所必須付出的代價。

從上面的過程當中,咱們還能夠理解在計算中round upround down的策略。當小數點後的數超過3 bit時,就是展示這個策略的時候。拿19舉例,表示成二進制科學計數法:$1.0011 * 2^4$。若是執行round up,最終的結果就是$1.010_{(2)} * 2^4 = 20_{(10)}$。若是執行round down,結果即是$1.001_{(2)} * 2^4 = 18$

若是咱們要提升浮點數表達的精度,mantissa區間就須要更多的bit來表示。拿float32來舉例,它是1 bitsign8 bitsexponent以及23 bits表示的mantissa

IEEE standard

該標準定義了更長的bit來提升表達的精度和範圍。

1. IEEE formats

它定義了上面描述的signexponentmantissa以及excess(就是8 bits表示過程當中用到的7)。

sign exponent mantissa exponent significant
format bit bits bits excess digits
Our 8-bit 1 4 3 7 1
Our 16-bit 1 6 9 31 3
IEEE 32-bit 1 8 23 127 6
IEEE 64-bit 1 11 52 1,023 15
IEEE 128-bit 1 15 112 16,383 34

2. 非數值

顧名思義:not a number,程序中偶爾會看到的NaN。好比0/0∞ + −∞等。這類數值在表示中exponent都是1。

3. 運算

討論 x + (y + z)(x + y) +z的結果是否相同,拿上面8 bits的浮點數表示來講明。其中x=1y=52z= -52。咱們注意到y+z = 0,因此第一個計算結果是1。但(x+y)的結果仍然是52,這主要是由於mantissa沒法表示,致使最終結果取近似值仍是52,最終結果是0。

另一個例子:1/6 + 1/6 + 1/6 + 1/6 + 1/6 + 1/6 = 1等式也是不存在的。在8 bits的表示中沒法準確的表示1/6,因此最終的結果要比1小。

在程序開發過程當中,咱們必須意識到這類問題產生的影響。

float to float

Round返回最近的整數,但返回值是一個float64類型。返回值是四捨五入後的結果。

a := math.Round(12.3456)
//output: 12

相對應的函數,還有FloorCeil

// Floor returns the greatest integer value less than or equal to x.
// output: 12
a := math.Floor(12.3456)

// Ceil returns the least integer value greater than or equal to x.
// output: 13
a := math.Ceil(12.3456)

match/big

關於浮點數的比較:

// change these value and play around
float1 := 123.4568
float2 := 123.45678

// convert float to type math/big.Float
var bigFloat1 = big.NewFloat(float1)
var bigFloat2 = big.NewFloat(float2)

// compare bigFloat1 to bigFloat2
result := bigFloat1.Cmp(bigFloat2)

參考文章:

  1. Golang : Compare floating-point numbers
  2. Floating-point representation
  3. Floating Point Numbers
相關文章
相關標籤/搜索