Java中解決浮點數精度的問題

問題描述

在項目中用Java作浮點數計算時,發現對於4.015*100這樣的計算,結果不是預料中的401.5,而是401.49999999999994。如此長的位數,對於顯示來講很不友好。java

問題緣由:浮點數表示

查閱相關資料,發現緣由是:計算機中的浮點數並不能徹底精確表示。例如,對於一個double型的38414.4來講,計算機是這樣存儲它的:數組

  1. 轉成二進制:1001011000001110.0110011001100110011001100110011001100
  2. 轉成科學計數法:1.0010110000011100110011001100110011001100110011001100×2^15
  3. double型編碼格式是這樣的:
    double 符號位1位 階碼11位 尾數52位
    1. 符號位:正數統一是0
    2. 階碼:15是正數,所以最高位是1,最低位減1,爲10000001110
    3. 尾數:去掉最高位默認的1,爲0010110000011100110011001100110011001100110011001100
  4. 組合起來,最終獲得的編碼是:0 10000001110 0010110000011100110011001100110011001100110011001100

從這裏能夠看出來,主要緣由在於二進制編碼使得小數部分沒法徹底精確表示,例如0.4 = 0.25 + 0.125 + ...,只能無限接近。因此在對浮點數作計算時會產生精度偏差。性能

解決辦法:高精度

Java中的BigDecimal能夠支持任意精度的浮點數運算。在《Effective Java 》這本書中建議:float 和double 用來作科學計算或者是工程計算,而在商業計算中使用java.math.BigDecimal 。編碼

BigDecimal有多種構造方法,如BigDecimal(double),BigDecimal(String),須要注意的是:構造參數爲String類型時才能保證不丟失精度,由於double類型自己就是不徹底精確的。故須要寫成這樣:BigDecimal("0.02")。spa

double類型的基本運算都能在BigDecimal中找到相對應的方法。另外,BigDecimal還能夠配合NumberFormat作格式化輸出。orm

BigDecimal在作運算的時候都會生成新的BigDecimal對象,所以相對double來講會帶來更多的性能開銷。對象

高精度實現初探

那麼BigDecimal是如何作到可以表示任意精度的呢?這裏只作一個初步的分析。ci

首先看BigInteger的實現。普通的int型是32位,所以有範圍限制。BigInteger中有成員變量int[] mag,這樣變長的int數組使得表示任意大小的整數成爲可能。源碼

再看BigDecimal的實現。它的官方介紹中說,任意一個BigDecimal均可以表示爲unscaledValue × 10^-scale的形式。unscaledValue是一個任意大小的整數,在源代碼中對應BigInteger intVal這個成員變量;scale是階數,在源代碼中對應int scale這個變量。這樣就在BigInteger的基礎上獲得了BigDecimal的實現。table

更細節的內容能夠自行閱讀源碼作進一步分析。

相關文章
相關標籤/搜索