浮點數為什么不精確?

姓名:牛康 ?學號: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 來做哪些要求精確的運算(例如銀行)時就要小心了,很可能得不到你想要的結果。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容