Java精度損失

對於Java的float和double類型,都存在精度損失的問題。
精度損失產生的原因在於Java的數據存儲採用的都是2進制形式,二進制不能準確的表示1/10等分數,只能無限趨近。

對比float,double,BigDecimal的精度:

public static void main(String[] args){
    float a = (float) 1.0;
    float b = (float) 0.965;
    double a1 = 1.0;
    double b1 = 0.965;
    BigDecimal a2 = new BigDecimal(a1);
    BigDecimal b2 = new BigDecimal(b1);
    BigDecimal a3 = new BigDecimal(new String("1.0"));
    BigDecimal b3 = new BigDecimal(new String("0.965"));

    System.out.println("float:"+(a-b));
    System.out.println("double:"+(a1-b1));
    System.out.println("BigDecimal use Double:"+a2.subtract(b2));
    System.out.println("BigDecimal use String:"+a3.subtract(b3));
}

 運行結果如下:
float:0.035000026
double:0.03500000000000003
BigDecimal use Double:0.03500000000000003108624468950438313186168670654296875
BigDecimal use String:0.035

總結:
1.float的精度損失最嚴重,然後依次是double,和使用參數類型爲double的BigDecimal。
2.使用BigDecimal中參數爲String類型的構造方法可以避免精度損失。

源碼分析:
BigDecimal是如何處理精度損失的,通過查看源碼發現:
針對傳入參數爲double類型和傳入參數爲String類型的處理方式有很大的不同。

傳入參數爲double類型:

//將一個double類型轉化爲long,該long類型的值中的第1個數字代表符號,後11個數字代表指數,最後52個數字代表具體的值
long valBits = Double.doubleToLongBits(val);
    //通過右移獲取符號
    int sign = ((valBits >> 63) == 0 ? 1 : -1);
    //通過右移獲取指數
    int exponent = (int) ((valBits >> 52) & 0x7ffL);
    //右移獲取有效數字
    long significand = (exponent == 0
            ? (valBits & ((1L << 52) - 1)) << 1
            : (valBits & ((1L << 52) - 1)) | (1L << 52));

例如使用 System.out.println(Double.doubleToLongBits(0.1)); 輸出的值爲4591870180066957722,將其轉化爲二進制,結果是11111110111001100110011001100110011001100110011001101000000000,將該二進制分割成三部分:
1(<-這個是符號) 11111101110(<-這個是指數) 0110011001100110011001100110011001100110100000000(<-這個是具體的值)
後續對指數和數值進行了額外的一些操作,比較多就沒仔細看,整體的思路是這樣。


傳入參數爲String類型:
對於參數爲String的情況,通過調試發現也差不多是使用指數,符號,有效數值來存儲。其中有這樣幾個參數:intCompact是有效數值,precision代表精度(每對數值的每一位進行操作精度加一),scale代表指數,例如3,則有效數字需乘上10^-3(當發現小數點後,每處理一位scale加一)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章