Go語言之父Rob Pike大神曾吐槽:不能掌握正則表達式或浮點數就不配當碼農!正則表達式
You should not be permitted to write production code if you do not have an journeyman license in regular expressions or floating point math.express
此前使用Java寫Spark SQL業務時,也有遇到浮點數比較問題即x>70的記錄行竟然出現了70的記錄,儘管SQL作了類型轉換再比較也無濟於事....spa
所以瞭解浮點數是頗有必要的喲~~code
電氣和電子工程師協會IEEE對於計算機浮點數的存儲、運算、表示等推出了IEEE754標準!blog
標準中規定:ip
float32位單精度浮點數在機器中表示用 1 位表示數字的符號,用 8 位表示指數,用 23 位表示尾數。it
double64位雙精度浮點數,用 1 位表示符號,用 11 位表示指數,52 位表示尾數。io
其中指數域也稱爲階碼。浮點數存儲字節定義如圖:console
尾數不爲0時,尾數域的最高有效位爲1,這稱爲規格化。不然,以修改階碼同時左右移動小數點位置的辦法,使其成爲規格化數的形式。class
浮點數x真值表示:
x=(−1)S×(1.M)×2e
float: e=E−127
double: e=E−1023
移碼是真值補碼的符號位取反,通常用做浮點數的階碼,目的是便於浮點數運算時的對階操做。
對於定點整數,計算機通常採用補碼的來存儲。
正整數的符號位爲0,反碼和補碼等同於原碼。
負整數符號位都爲1,原碼,反碼和補碼的表示都不相同,由負數原碼錶示法變成反碼和補碼有以下規則:
(1)原碼符號位爲1不變,整數的每一位二進制數位求反得反碼;
(2)反碼符號位爲1不變,反碼數值位最低位加1得補碼。
好比,以一個字節來表示-3,那麼[−3]原=10000011 [−3]反=11111100 [−3] 補=11111101 [−3]移=01111101
【3.14的單精度浮點數表示】
首先將3.14轉成二進制:
整數部分3的二進制是11
小數部分0.14的二進制是:0.0010001111010111000010[10001111.....](方括號中表示小數點後第23位及以後)
這樣,3.14的二進制代碼就是:11.0010001111010111000010[10001111....]×20
那麼用正規化表示就是:1.10010001111010111000010[10001111....]×21
方括號表示的超出23位以後的二進制部分,因爲單精度浮點數尾數只有23位,因此須要舍入(舍入方法見後)
因爲第24位爲1,且以後 不全爲 0,因此須要向第23位進1完成上舍入:1.10010001111010111000011×21
而其指數是1,須要加上移碼127,即128,也就是1000 0000
它又是正數,因此符號爲0
綜上所述,3.14的單精度浮點數表示爲:
0 1000-0000 1001-0001-1110-1011-1000-011S符號位 0
e指數位 1000-0000
M尾數位 1001-0001-1110-1011-1000-011
十六進制代碼爲:0x4048F5C3
經過栗子可知,3.14的單精度浮點數表示是0 1000-0000 1001-0001-1110-1011-1000-011。如今咱們來還原,看看它的偏差:
指數是128,那麼還原回去(減去移碼),實際指數就是1
尾數還原也就是:10010001111010111000011,因此正規化形式是:1.10010001111010111000011×21
也就是11.0010001111010111000011
利用二進制轉十進制,可得它對應的十進制數是:3.1400001049041748046875 不等於3.14
這就是爲何浮點數運算結果在業務代碼中老是不可確切預期的緣由!!!!
機器ε表示1與大於1的最小浮點數之差。例如雙精度表示1和表示大於1的最小浮點數
雙精度浮點數的機器ε = 2-52 ≈ 2.220446049250313e-16
同理,單精度的機器ε = 2-23 ≈ 1.1920928955078125e-7
在舍入規則中,相對舍入偏差不能大於機器ε的一半。
單精度浮點數爲例
(1)0的表示
對於階碼爲0或255的狀況,IEEE754標準有特別的規定:
若是 階碼E=0而且尾數M是0,則這個數的真值爲±0(正負號和數符位有關)。
+0的機器碼爲:0 00000000 000 0000 0000 0000 0000 0000
-0的機器碼爲:1 00000000 000 0000 0000 0000 0000 0000
須要注意一點,浮點數不能精確表示0,而是以很小的數來近似表示0。由於浮點數的真值等於
x=(−1)S×(1.M)×2e
e=E−127
那麼
+0的機器碼真值爲 1.0×2−127
-0機器碼真值爲 −1.0×2−127
(2)無窮的表示
若是階碼E=255 而且尾數M全是0,則這個數的真值爲±∞(一樣和符號位有關)。
所以
+∞的機器碼爲:0 11111111 000 0000 0000 0000 0000 0000
-∞的機器嗎爲:1 11111111 000 0000 0000 0000 0000 0000
(3)NaN(Not a Number)
若是 E = 255 而且 M 不全是0,則這不是一個數(NaN)。
以23位尾數位的單精度浮點數爲例,舍入時須要重點參考第24位
若第24位爲1,且第24位以後所有爲0。此時就要使第23位爲0:若第23位原本就是0則無論,若第23位爲1,則第24位就要向第23位進一位,這樣第23位就能夠爲0
若第24位爲1,且第24位以後不全爲0,則第24位就要向第23位進一完成上舍入。
若第24位爲0,此時直接捨去不進位,稱爲下舍入。
JavaScript console 雙精度浮點數
>>9.4 - 9 - 0.4 === 0
<<false
>>(9.4-9-0.4).toFixed(20)
<<"0.00000000000000033307"9.4-9-0.4不嚴格等於0,其運算結果偏差。
由於按照上面的浮點數知識可知
9.4在機器內被表示爲:9.4+0.2×2-49
0.4被表示爲:0.4+0.1×2-52
當9.4-9時(由於9是整數是能夠精確存儲的)得0.4+0.2×2-49,再減去0.4+0.1×2-52得3×2-53,約等於"0.00000000000000033307"。
詳細解釋:
9的二進制是1001,而0.4的二進制是0.0110-0110-0110-……無限循環的。從而9.4的二進制是1001.0110-0110……,正規化之後就變成 1.001-0110-0110-……×2^3,
由於雙精度浮點數是52位尾數,因此小數部分保留0.001-0110-0110-……-0110-0 [110-0110-0110-……]。即001後跟12個0110循環節,而後第52位是0,中括號表示從
第53位起開始捨棄的部分。根據我提到的舍入規則,第53位1且後面不全爲0,要向第52位完成上舍入,因此小數部分就變成 0.001-0110-0110-……-0110-1。至此咱們
能夠看到,這個數較之9.4,因爲小數部分第52位由0變爲1,因此多加了2-52,可是由於從小數部分第53位開始捨棄了,捨棄部分是 0.1100-1100-…×2-52 = 0.8×2-52。
因此咱們多加了2-52,可是少了0.8×2-52,這就意味着,但考慮尾數部分,這個數比9.4多了 2-52 - 0.8×2-52 = 0.2×2-52,別忘記以前還有一個2^3,因此整
體多了0.2×2-52×2^3 = 0.2×2-49
這就是爲何9.4在機器內被表示爲:9.4+0.2×2-49
同理,0.4在機器內被表示爲:0.4+0.1×2-52