姓名:牛康 ?學號:17101223416
轉載自:https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513140&idx=1&sn=565517e977ac56904305a4a9f9d65012&scene=38#wechat_redirect
【嵌牛導讀】:在日常的編碼過程中經常會看到浮點數的存在,那浮點數到底精不精確呢?
【嵌牛鼻子】:浮點數
【嵌牛提問】:浮點數的正確玩法是怎樣的呢?
【嵌牛正文】:
一、浮點數為什么不精確?
其實這句話本身就不精確, 相對精確一點的說法是: 我們碼農在程序里寫的10進制小數,計算機內部無法用二進制的小數來精確的表達。
什么是二進制的小數? 就是形如 101.11 ?數字,注意,這是二進制的,數字只能是0和1。
101.11 就等于 1 * 2^2 +0 *2^1 + 1*2^0 + 1*2^-1 + 1*2^-2 ?= 4+0+1+1/2+1/4 = 5.75
下面的圖展示了一個二進制小數的表達形式。
從圖中可以看到,對于二進制小數,小數點右邊能表達的值是 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, ?1/128 ... ?1/(2^n)
現在問題來了, 計算機只能用這些個?1/(2^n) 之和來表達十進制的小數。
我們來試一試如何表達十進制的 0.2 吧。
0.01 ?= 1/4 ?= 0.25 ?,太大
0.001 =1/8 = 0.125 , 又太小
0.0011 ? = 1/8 + 1/16 = 0.1875 , 逼近0.2了
0.00111 = 1/8 + 1/16 + 1/32 = 0.21875 ?, 又大了
0.001101 = 1/8+ 1/16 + 1/64 = 0.203125 ?還是大
0.0011001 = 1/8 + 1/16 + 1/128 =??0.1953125 ?這結果不錯
0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875
已經很逼近了, 就這樣吧。
這就是我說的用二進制小數沒法精確表達10進制小數的含義。
二、浮點數的計算機表示
那計算機內部具體是怎么表示的呢?
計算機不可能提供無限的空間讓程序去存儲這些二進制小數。
它需要規定長度, ?在Java 中, 提供了兩種方式: float 和double , 分別是32位和64位。
可以這樣查看一下一個float的內部表示(以0.09f為例):
Float.floatToRawIntBits(0.09f)
你將會得到:1035489772, 這是10進制的, 轉化成二進制, 在前面加幾個0補足 32位就是:
0?01111011?01110000101000111101100
你可以看到它分成了3段:
第一段代表了符號(s) ?: ?0 正數, ?1 負數 ?, 其實更準確的表達是 (-1) ^0
第二段是階碼(e):01111011 ,對應的10進制是 123
第三段是尾數(M)
你看到了尾數和階碼,就會明白這其實是所謂的科學計數法:
(-1)^s ?* M * ?2^e
對于0.09f 的例子,就是:
0101110000101000111101100 * (2^123)
好像不對,這肯定遠遠大于0.09f ?!
這是因為浮點數遵循的是IEEE754 表示法, 我們剛才的s(符號) 是對的,但是 e(階碼)和 M(尾數)需要變換:
對于階碼e , 一共有8位, 這是個有符號數, 特別是按照IEEE754 規范,如果不是0或者255, 那就需要減去一個叫偏置量的值,對于float 是127
所以 E ?= e - 127 = 123-127 = -4
對于尾數M ,如果階碼不是0或者255,??他其實隱藏了一個小數點左邊的一個 1 (節省空間,充分壓榨每一個bit啊)。
即 M = 1.01110000101000111101100
現在寫出來就是:
1.01110000101000111101100 * 2^-4
=0.000101110000101000111101100
=?1/16 + 1/64 + 1/128+ 1/256 + ....
=?0.0900000035762786865234375
你看這就是0.09的內部表示, 很明顯他比0.09更大一些, 是不精確的!
64位的雙精度浮點數double是也是類似的, 只是尾數和階碼更長, 能表達的范圍更大。
符號位 :1位
階碼 : 11位
尾數: 52位
上面的例子0.09f 其實是所謂的規格化的浮點數, 還有非規格化的浮點數,這里就不展開了。
三、使用浮點數
由于浮點數表示的這種“不精確性”或者說是“近似性”, 對于精確度要求不高的運算還行, 如果我們用float或者double 來做哪些要求精確的運算(例如銀行)時就要小心了,很可能得不到你想要的結果。