很多人都知道,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(單位為分)來表示金額。