面試官瘋了嗎,問我為什么浮點數不精確?
很多人都知道,Java 中的浮點數并不精確,需要用 BigDecimal進行精確計算,但是,很少有人知道為什么浮點數不精確呢?不精確為什么還要用呢?本文就來展開分析一波。
我們知道,計算機的數字的存儲和運算都是通過二進制進行的,對于,十進制整數轉換為二進制整數采用"除2取余,逆序排列法。
具體做法是:
- 用2整除十進制整數,可以得到一個商和余數;
- 再用2去除商,又會得到一個商和余數,如此進行,直到商為小于1時為止
- 然后把先得到的余數作為二進制數的低位有效位,后得到的余數作為二進制數的高位有效位,依次排列起來。
如,我們想要把127轉換成二進制,做法如下:
那么,十進制小數轉換成二進制小數,又該如何計算呢?
十進制小數轉換成二進制小數采用"乘2取整,順序排列"法。
具體做法是:
- 用2乘十進制小數,可以得到積
- 將積的整數部分取出,再用2乘余下的小數部分,又得到一個積
- 再將積的整數部分取出,如此進行,直到積中的小數部分為零,此時0或1為二進制的最后一位。或者達到所要求的精度為止。
如嘗試將0.625轉成二進制:
但是0.625是一個特列,用同樣的算法,請計算下0.1對應的二進制是多少:
我們發現,0.1的二進制表示中出現了無限循環的情況,也就是(0.1)10 = (0.000110011001100…)2
這種情況,計算機就沒辦法用二進制精確的表示0.1了。
所以,為了解決部分小數無法使用二進制精確表示的問題,于是就有了IEEE 754規范。
IEEE二進制浮點數算術標準(IEEE 754)是20世紀80年代以來最廣泛使用的浮點數運算標準,為許多CPU與浮點運算器所采用。
浮點數和小數并不是完全一樣的,計算機中小數的表示法,其實有定點和浮點兩種。因為在位數相同的情況下,定點數的表示范圍要比浮點數小。所以在計算機科學中,使用浮點數來表示實數的近似值。
IEEE 754規定了四種表示浮點數值的方式:單精確度(32位)、雙精確度(64位)、延伸單精確度(43比特以上,很少使用)與延伸雙精確度(79比特以上,通常以80位實現)。
其中最常用的就是32位單精度浮點數和64位雙精度浮點數。
單精度浮點數在計算機存儲器中占用4個字節(32 bits),利用“浮點”(浮動小數點)的方法,可以表示一個范圍很大的數值。
比起單精度浮點數,雙精度浮點數(double)使用 64 位(8字節) 來存儲一個浮點數。
IEEE并沒有解決小數無法精確表示的問題,只是提出了一種使用近似值表示小數的方式,并且引入了精度的概念。
一個浮點數a由兩個數m和e來表示:a = m × b^e。
在任意一個這樣的系統中,我們選擇一個基數b(記數系統的基)和精度p(即使用多少位來存儲)。m(即尾數)是形如±d.ddd…ddd的p位數(每一位是一個介于0到b-1之間的整數,包括0和b-1)。
如果m的第一位是非0整數,m稱作規格化的。有一些描述使用一個單獨的符號位(s 代表+或者-)來表示正負,這樣m必須是正的。e是指數。
最后,由于計算機中保存的小數其實是十進制的小數的近似值,并不是準確值,所以,千萬不要在代碼中使用浮點數來表示金額等重要的指標。
建議使用BigDecimal或者Long(單位為分)來表示金額。