java中double和float精度丟失問題

爲何會出現這個問題呢,就這是java和其它計算機語言都會出現的問題,下面咱們分析一下爲何會出現這個問題:
float和double類型主要是爲了科學計算和工程計算而設計的。他們執行二進制浮點運算,這是爲了在普遍的數字範圍上提供較爲精確的快速近似計算而精心設計的。然而,它們並無提供徹底精確的結果,因此咱們不該該用於精確計算的場合。float和double類型尤爲不適合用於貨幣運算,由於要讓一個float或double精確的表示0.1或者10的任何其餘負數次方值是不可能的(其實道理很簡單,十進制系統中能不能準確表示出1/3呢?一樣二進制系統也沒法準確表示1/10)。

浮點運算不多是精確的,只要是超過精度能表示的範圍就會產生偏差。每每產生偏差不是由於數的大小,而是由於數的精度。所以,產生的結果接近但不等於想要的結果。尤爲在使用 float 和 double 做精確運算的時候要特別當心。

如今咱們就詳細剖析一下浮點型運算爲何會形成精度丟失?java

 

首先咱們要搞清楚下面兩個問題:

     (1) 十進制整數如何轉化爲二進制數

           算法很簡單。舉個例子,11表示成二進制數:

                     11/2=5 餘   1

                       5/2=2   餘   1

                       2/2=1   餘   0

                       1/2=0   餘   1

                          0結束         11二進制表示爲(從下往上):1011

          這裏提一點:只要遇到除之後的結果爲0了就結束了,你們想想,全部的整數除以2是否是必定可以最終獲得0。換句話說,全部的整數轉變爲二進制數的算法會不會無限循環下去呢?絕對不會,整數永遠能夠用二進制精確表示 ,但小數就不必定了。

      (2) 十進制小數如何轉化爲二進制數

           算法是乘以2直到沒有了小數爲止。舉個例子,0.9表示成二進制數

                     0.9*2=1.8   取整數部分 1

                     0.8(1.8的小數部分)*2=1.6    取整數部分 1

                     0.6*2=1.2   取整數部分 1

                     0.2*2=0.4   取整數部分 0

                     0.4*2=0.8   取整數部分 0

                     0.8*2=1.6 取整數部分 1

                     0.6*2=1.2   取整數部分 0

                              .........      0.9二進制表示爲(從上往下): 1100100100100......

           注意:上面的計算過程循環了,也就是說*2永遠不可能消滅小數部分,這樣算法將無限下去。很顯然,小數的二進制表示有時是不可能精確的 。其實道理很簡單,十進制系統中能不能準確表示出1/3呢?一樣二進制系統也沒法準確表示1/10。這也就解釋了爲何浮點型減法出現了"減不盡"的精度丟失問題。

解決方法算法

使用BigDecmal,並且須要在構造參數使用String類型。ide

在《Effective Java》這本書中就給出了一個解決方法。該書中也指出,float和double只能用來作科學計算或者是工程計算,在商業計算等精確計算中,咱們要用java.math.BigDecimal。工具

    BigDecimal類有4個構造方法,咱們只關心對咱們解決浮點型數據進行精確計算有用的方法,即spa

BigDecimal(double value) // 將double型數據轉換成BigDecimal型數據設計

    思路很簡單,咱們先經過BigDecimal(double value)方法,將double型數據轉換成BigDecimal數據,而後就能夠正常進行精確計算了。等計算完畢後,咱們能夠對結果作一些處理,好比 對除不盡的結果能夠進行四捨五入。最後,再把結果由BigDecimal型數據轉換回double型數據。blog

    這個思路很正確,可是若是你仔細看看API裏關於BigDecimal的詳細說明,你就會知道,若是須要精確計算,咱們不能直接用double,而非要用 String來構造BigDecimal不可!因此,咱們又開始關心BigDecimal類的另外一個方法,即可以幫助咱們正確完成精確計算的 BigDecimal(String value)方法。ip

// BigDecimal(String value)可以將String型數據轉換成BigDecimal型數據ci

    那麼問題來了,想像一下吧,若是咱們要作一個浮點型數據的加法運算,須要先將兩個浮點數轉爲String型數據,而後用 BigDecimal(String value)構形成BigDecimal,以後要在其中一個上調用add方法,傳入另外一個做爲參數,而後把運算的結果(BigDecimal)再轉換爲浮 點數。若是每次作浮點型數據的計算都要如此,你可以忍受這麼煩瑣的過程嗎?至少我不能。因此最好的辦法,就是寫一個類,在類中完成這些繁瑣的轉換過程。這 樣,在咱們須要進行浮點型數據計算的時候,只要調用這個類就能夠了。網上已經有高手爲咱們提供了一個工具類Arith來完成這些轉換操做。它提供如下靜態 方法,能夠完成浮點型數據的加減乘除運算和對其結果進行四捨五入的操做:it

package com.util;
import java.math.BigDecimal;

/**
 * 因爲Java的簡單類型不可以精確的對浮點數進行運算,這個工具類提供精確的浮點數運算,包括加減乘除和四捨五入。
 */
public class Arith {

	//默認吃吃飯運算精度
	private static final int DEF_DIV_SCALE = 10;

	//這個類不能實例化
	private Arith() {
		
	}

	/**
	 * 提供精確的加法運算
	 * 
	 * @param v1
	 *            被加數
	 * @param v2
	 *            加數
	 * @return 兩個參數的和
	 */
	public static double add(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.add(b2).doubleValue();
	}

	/**
	 * 提供精確的減法運算
	 * @param v1
	 *            被減數
	 * @param v2
	 *            減數
	 * @return兩個參數的差
	 */
	public static double sub(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.subtract(b2).doubleValue();
	}

	/**
	 * 提供精確的乘法運算
	 * 
	 * @param v1
	 *            被乘數
	 * @param v2
	 *            乘數
	 * @return 兩個參數的積
	 */
	public static double mul(double v1, double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.multiply(b2).doubleValue();
	}

	/**
	 * 提供(相對)精確的除非運算,當發生除不盡的狀況時,精確到小數點之後10位,之後的數字四捨五入
	 * @param v1
	 *            被除數
	 * @param v2
	 *            除數
	 * @return 兩個參數的商
	 */
	public static double div(double v1, double v2) {
		return div(v1, v2, DEF_DIV_SCALE);
	}

	/**
	 * 提供(相對)精確的除法運算。當發生除不盡的狀況時,由scale參數指定精度,之後的數字四捨五入
	 * @param v1
	 *            被除數
	 * @param v2
	 *            除數
	 * @param scale
	 *            表示表示須要精確到小數點以後位數。
	 * @return 兩個參數的商
	 */
	public static double div(double v1, double v2, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException(
					"The scale must be a positive integer or zero");
		}
		BigDecimal b1 = new BigDecimal(Double.toString(v1));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
	}

	/**
	 * 提供精確的小數位四捨五入處理。
	 * 提供精確的小數位四捨五入處理
	 * 
	 * @param v
	 *            須要四捨五入的數位
	 * @param scale
	 *            小數點後保留幾位
	 * @return 四捨五入後的結果
	 */
	public static double round(double v, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException(
					"The scale must be a positive integer or zero");
		}
		BigDecimal b = new BigDecimal(Double.toString(v));
		BigDecimal one = new BigDecimal("1");
		return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
	}
}

  

附上Arith的源代碼,你們只要把它編譯保存好,要進行浮點數計算的時候,在你的源程序中導入Arith類就可使用以上靜態方法來進行浮點數的精確計算了。

相關文章
相關標籤/搜索