關於浮點數,爲何它生來就可能存在偏差?帶着好奇查閱了一些介紹,而後作了簡單彙總。這只是一段知識的開始,後續還會繼續完善。(很難過,這裏的MarkDown不支持編輯公式。請移駕本人的博客地址: http://neojos.com )—— 蕩蕩上帝,下民之闢。疾威上帝,其命多闢。天生烝民,其命匪諶。靡不有初,鮮克有終。git
浮點數在計算機中是如何表示的?由於它有小數點,那麼小數點後面的數,該如何用二進制來表示呢?咱們都知道,浮點數自己就存在偏差,在工做中,你所使用的float
都是一個近似數。這又是什麼緣由致使的呢?golang
1. Fixed-point
fixed-point
是將bit
位拆分紅固定的兩部分:小數點前的部分和小數點後的部分。拿32 bit
的fixed-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 011
。code
再來一個decode
的例子,即將0 0101 100
還原回原始值。根據以前的描述0101
表示的十進制是5,因此exponent = -2
,表示回二進制科學計數法的結果:$1.100 * 2^{-2} = 0.011_{(2)}$
。咱們繼續轉換成真實精度的數:0.375
。orm
最後能夠看在,若是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 bits
的mantissa
放不下如今的10011
。咱們不得不作近似取值,將結果修改成$1.101_{(2)} * 2^{5} = 110100_{(2)} = 52_{(10)}$
。因此,在咱們8 bits
表達的浮點數中51 = 52
。這樣的處理有時候讓咱們很無奈,但這也是爲了讓8 bits
表示更大範圍的數所必須付出的代價。
從上面的過程當中,咱們還能夠理解在計算中round up
和round 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 bit
的sign
,8 bits
的exponent
以及23 bits
表示的mantissa
。
IEEE standard
該標準定義了更長的bit
來提升表達的精度和範圍。
1. IEEE formats
它定義了上面描述的sign
、exponent
、mantissa
以及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=1
,y=52
,z= -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小。
在程序開發過程當中,咱們必須意識到這類問題產生的影響。
Round
返回最近的整數,但返回值是一個float64
類型。返回值是四捨五入後的結果。
a := math.Round(12.3456) //output: 12
相對應的函數,還有Floor
和Ceil
// 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)
參考文章: