精確運算避免使用float和double

在《你可能不知道的Java基礎知識(一)》中,我提到使用浮點運算要慎重,感受說的不夠透徹,其實float和double類型主要是爲科學和工程計算而設計的。他們執行的是二進制浮點運算,因爲二進制的侷限性,有時候沒法獲得準確的結果。java


例如:System.out.println(2.0-1.1)將輸出0.8999999999999999,而不是0.9,固然這在科學計算中可有可無,經過四捨五入就能夠輕鬆解決問題,可是在禁止出現舍入偏差的運算中(好比金融計算)就不適用了。ide


在二進制中沒法精確地表示10的任何負數次方值,好比0.1,這和十進制中沒法精確表示1/3一個道理,因此越到像金融貨幣計算的問題,咱們不得不捨棄float和double,而改BigDecimal類。spa


在《Effective Java》這本書中也提到這個原 則,float和double只能用來作科學計算或者是工程計算,在商業計算中咱們要用java.math.BigDecimal,並且非要用 String來夠造BigDecimal不可!在《Effective Java》一書中的例子是用String來夠造BigDecimal的,可是書上 卻沒有強調這一點,這也許是一個小小的失誤吧。設計

 

用BigDecimal解決上述2.0-1.1問題的代碼爲:code

import java.math.*;
class BigDecimalTest 
{
    public static void main(String[] args) 
    {
        //使用字符串構造BigDecimal對象
        BigDecimal a =new BigDecimal("2.0");
        BigDecimal b = new BigDecimal("1.1");
        System.out.println(a.subtract(b));
    }
}

輸出:0.9

假如不用String來構造BigDecimal問題將依舊:對象

import java.math.*;
class TestB 
{
    public static void main(String[] args) 
    {
        //使用double來構造BigDecimal,問題依舊
        BigDecimal a = new BigDecimal(2.0);
        BigDecimal b = new BigDecimal(1.1);
        System.out.println(a.subtract(b));
    }
}
輸出 0.899999999999999911182158029987476766109466552734375

固然BigDecimal也提供了舍入方法,下面是網上淘來的類,完美的解決了這些問題:ip

package com.morningstar.bigdecimal;

import java.math.BigDecimal;

public class Arithmetic4Double {
    //默認除法運算精度
    private static final int DEF_DIV_SCALE = 10;
   
    //全部方法均用靜態方法實現,不容許實例化
    private Arithmetic4Double() {}

    /**
     * 實現浮點數的加法運算功能
     * @param v1 加數1
     * @param v2 加數2
     * @return v1+v2的和
     */
    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 v1-v2的差
     */
    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 v1×v2的積
     */
    public static double multi(double v1,double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 實現浮點數的除法運算功能
     * 當發生除不盡的狀況時,精確到小數點之後DEF_DIV_SCALE位(默認爲10位),後面的位數進行四捨五入。
     * @param v1 被除數
     * @param v2 除數
     * @return v1/v2的商
     */
    public static double div(double v1,double v2) {
     BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,DEF_DIV_SCALE,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 實現浮點數的除法運算功能
     * 當發生除不盡的狀況時,精確到小數點之後scale位,後面的位數進行四捨五入。
     * @param v1 被除數
     * @param v2 除數
     * @param scale 表示須要精確到小數點之後幾位
     * @return v1/v2的商
     */
    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();
    }
}

總結一句話:精確運算要用String構造的BigDecimal,不要用float和double!ci

相關文章
相關標籤/搜索