1. 程式人生 > >浮點數計算精度丟失問題#W01

浮點數計算精度丟失問題#W01

前幾日電話面試,被問到一個問題: “浮點數計算精度丟失的原因是什麼?”啊,啥? 我一臉懵逼,“就是在irb中,0.2+0.4不等於0.6,為什麼?“,面試官又補了一句。我沒有真正get到問題是什麼,胡說了一通。

顯然,這是問到了我的知識盲區了,我從來沒有遇到過這個問題。於是我開啟python命令列,執行了一下:

>>> 0.4 + 0.2
0.6000000000000001
>>>

我又開啟Chrome,進入console

0.2 + 0.4

0.6000000000000001

一下子,我get到了這個問題,對於其原因,當然我還是懵逼的,於是我搜索了一下這個問題。瀏覽過了一些資料,這個問題極大的引起了我的興趣,除了問題本身所涉及的知識,還有在

這篇文章中,我被觸動了,因為他說把理科當文科學了的人,我也在內。對於知識的學習,浮躁虛於表面,很多時候只記住了一個結論,但是底層的原因,卻並沒有去深入探知。對於學習的態度和方式,我也從頭反思了一下,摒棄走馬觀花式的學習,用寫作的方式將知識進行結構化,深入到細節。一個點一個點地學習知識,努力做到對於知識的表達能做到: 蔡崇信為什麼敢冒這風險 這篇文章中所提到的:”優雅、簡潔、有邏輯的表達“。

對於自己這方面的積累和刻意塑造,我想就從本篇文章開始,從這個有意思的問題開始:

為什麼浮點數計算時會有精度丟失?

什麼是浮點數?

浮點數是計算機表示有理數的一種方式,或者說規範。浮點數和定點數相對應。

什麼是定點數呢? 這兩個詞中的‘點’也就是常說的小數點。定點數就是計算機在表示數字時,小數點的位置是固定的。

比如計算機用2位元組二進位制數來表示數字,它怎麼表示呢? (這裡只是演示概念)

2個位元組有16位, 把前8位用來表示整數部分,後8位用來表示小數部分,也就是說小數點在了第8位,當然具體定在多少位是可以設定的。用這種方式表示數字8.5時就是這樣:

00001000.00001001

這樣子,2位元組的二進位制只能表達2^8 =256個小數,而且範圍有限(0 - 2^8),很多時候會浪費空間。有時候我的整數部分比較長,小數部分短,有時候反之。按照之前的方式,範圍就會被限制,300.5 這種就沒法被表示,需要4位元組的空間來表示,那麼這就很浪費了。

於是浮點數就出現了。

浮點數在表示的時候,小數點的位置可以根據實際情況移動。


Sign表示是正數還是負數,先不管。

Exponent表示指數部分,和 1.34*10^N 這個數中的N的性質一樣,只是前面這個數的基數是10,它的基數是2 。這個部分的值會決定浮動的小數點到底定在哪個位置。

Mantissa就是實際的數字部分。

現在用浮點數來表示 5.2 ,整數部分是 101,小數部分是0.2,用二進位制表示就是

0.2 * 2 = 0.4    0

0.4*2 = 0.8      0

0.8*2 = 1.6      1

0.6*2 = 1.2      1

0.2*2 = 0.4      0

出來的結果就是 101.001100110011(這裡是無限迴圈,暫時只擷取一部分)

現在將上面這串數字填進上面那張圖上:

Sign: 0   因為是正數

Exponent: Exponent表示指數N的值是多少,也就是 1.1232 * 2^N的這個N的值。這裡要注意,因為N的值可為正,也可以為負。也就是說可以表達1.1232 * 2^5, 也可以表達1.1232 * 2^-5 這種數字,因為只有8個bit來表達Exponent, Exponent的取值範圍就是-127~127, 要用8個bit表達這個範圍,8個bit的範圍是0~255,也就是 0~127 表示 -127~0, 127~255表示0~127 。要表示N=1,則需要加上127的基數。也就是說 N=1 時,Exponent為 10000001。舉個反例,N=-1時,Exponent 為 00000001 。浮點數標準的表示正規化是: 小數點的左邊只有一個1,也就是說101.001100110011要表示成1.01001100110011 *2 ^2, 這裡的N=2。它意味著這裡小數點動態地定在了第三個數字這裡。

Mantissa: 1.01001100110011001100110 (只有23個bit來表示)

所以填充之後的整體就是這樣的:

0 10000010 01001100110011001100110  (共32位)

反向提取, 當看到上面這串二進位制時,我知道這幾個資料: 為正數; N=2,實際的浮點數是:1.01001100110011001100110 * 2^2

先說說十進位制 

說完浮點數的概念,先來看看十進位制。 123 在十進位制中我們知道是100 + 20 + 3,每往左一位,就會在10的冪上加一,往右,會在10的冪上減一。

同樣的在二進位制中,0101 表示十進位制中的5, 5=0*2^3 + 1*2^2 + 0*2^1 + 1*2^0 , 每往左一位,會在2的冪上加一。

十進位制不能表達分數

十進位制可以表達100,12345,但十進位制能表達 1/3 嗎?

答案是不能。 為什麼? 因為1/3是無限迴圈小數,表達不了,當精度不夠的時候,後面會被截斷。

會發現只有當分母的因式分解中有2或者5,那麼就可以表達成有限小數,也就是可能被10進製表達。

2*5=10 ,這是10進位制的限制。

二進位制不能表達大部分浮點數

看了上面十進位制不能表達部分分數,就可以理解二進位制不能表達很多浮點數了。除了表達範圍的問題,很多小數在用二進位制表達的時候會出現無限迴圈的情況。就像文章最上面的0.2,上面已經推導過,0.2得到的就是0.001100110011·····, 當後面的重複迴圈長度超過了計算機所能表達的範圍時,它就會被截斷。

也就是說

>>> 0.4 + 0.2
0.6000000000000001
>>>

0.4 和 0.2 在被計算機計算的時候,其值會被表達為近似值,精度在做運算之前就已經丟失了,結果肯定就變樣了。

彌補方法

突然想起了,之前做支付介面的時候,會疑惑為什麼要把人民幣單位乘以100弄成分,而不是元。當時如果深入想想這個問題,也不至於現在才知道計算機領域這種最基礎的常識了。

>>> 0.41 - 0.01
0.39999999999999997

不可思議。