天天看點

Java中的小數運算與精度損失

Java中的小數運算與精度損失

float、double類型的問題

我們都知道,計算機是使用二進制存儲資料的。而平常生活中,大多數情況下我們都是使用的十進制,是以計算機顯示給我們看的内容大多數也是十進制的,這就使得很多時候資料需要在二進制與十進制之間進行轉換。對于整數來說,兩種進制可以做到一一對應。而對于小數來講就不是這樣的啦。

我們先來看看十進制小數轉二進制小數的方法

對小數點以後的數乘以2,會得到一個結果,取結果的整數部分(不是1就是0),然後再用小數部分再乘以2,再取結果的整數部分……以此類推,直到小數部分為0或者位數已經夠了。順序取每次運算得到的整數部分,即為轉換後的小數部分。

示範:

0.125 ×2=0.25 .......................0

0.25×2=0.5.............................0

0.5×2=1.0................................1

即 0.125的二進制表示為小數部分為0.001

其實我們可以看出,這種方法實質上就是用1/2,1/4,8/1...來組合加出我們要轉換的資料值,但顯然不是所有的數都能夠組合出來的。如0.1。

0.1×2=0.2 .....................0

 0.2×2=0.4 ......................0

 0.4×2=0.8 .....................0

 0.8×2=1.6.......................1

 0.6×2=1.2.......................1

 0.2×2=0.4.......................0

 .....

從上述計算過程我們可以看出,這是個無限小數,是以在這種情況下我們的float、double隻能舍去一些位。

那為什麼我們在直接給float指派在輸出時沒有看到精度損失而在運算時卻會出現呢?

确實是這樣,如下

float a = 0.2f;

System.out.println(a);

//輸出0.2

對于上述情況我隻是查了資料,好像是因為編譯器會進行優化,當我們存儲的資料特别接近的時候,編譯器會很貼心的傳回我們想看到的數值(即二進制浮點數并不能準确的表示0.1這個十進制小數,它使用了0.100000001490116119384765625來代替0.1。),至于到了運算中,就會出現精度損失較大進而看到了真相。如果這塊說的不對歡迎小夥伴們在評論區指正!

解決方法

BigDecimal 原理

我們一般會使用

BigDecimal 來避免出現精度丢失問題,至于為什麼BigDecimal 可以避免,而float或double不行,我們在此不詳細讨論,簡單來說就是BigDecimal 通過借助整數來表示小數的方式,因為對于整數而言,二進制和十進制是完全一一對應的,用整數來表示小數,再記錄下小數的位數,就可以完美的解決該問題。

BigDecimal 用法

java.math.BinInteger 類和 java.math.BigDecimal 類都是Java提供的用于高精度計算的類.其中 BigInteger 類是針對大整數的處理類,而 BigDecimal 類則是針對大小數的處理類.

BigDecimal構造方法

BigDecimal BigDecimal(double d); //不允許使用

BigDecimal BigDecimal(String s); //常用,推薦使用

static BigDecimal valueOf(double d); //常用,推薦使用

double 參數的構造方法,不允許使用!!!!因為它不能精确的得到相應的值;

String 構造方法是完全可預知的: 寫入 new BigDecimal("0.1") 将建立一個 BigDecimal,它正好等于預期的0.1; 是以,通常建議優先使用 String 構造方法;

靜态方法 valueOf(double val) 内部實作,仍是将 double 類型轉為 String 類型; 這通常是将 double(或float)轉化為 BigDecimal 的首選方法;

測試

System.out.println(new BigDecimal(0.1));

System.out.println(BigDecimal.valueOf(0.1));

\輸出*

0.1000000000000000055511151231257827021181583404541015625

BigDecimal常用操作

我們通過一個工具類源碼來體會BigDecimal的正常用法

package com.util;

import java.math.BigDecimal;

/**

  • 提供精确的浮點數運算(包括加、減、乘、除、四舍五入)工具類

    */

public class ArithUtil {

// 除法運算預設精度
private static final int DEF_DIV_SCALE = 10;

private ArithUtil() {

}

/**
 * 精确加法
 */
public static double add(double value1, double value2) {
    BigDecimal b1 = BigDecimal.valueOf(value1);
    BigDecimal b2 = BigDecimal.valueOf(value2);
    return b1.add(b2).doubleValue();
}

/**
 * 精确減法
 */
public static double sub(double value1, double value2) {
    BigDecimal b1 = BigDecimal.valueOf(value1);
    BigDecimal b2 = BigDecimal.valueOf(value2);
    return b1.subtract(b2).doubleValue();
}

/**
 * 精确乘法
 */
public static double mul(double value1, double value2) {
    BigDecimal b1 = BigDecimal.valueOf(value1);
    BigDecimal b2 = BigDecimal.valueOf(value2);
    return b1.multiply(b2).doubleValue();
}

/**
 * 精确除法 使用預設精度
 */
public static double div(double value1, double value2) throws IllegalAccessException {
    return div(value1, value2, DEF_DIV_SCALE);
}

/**
 * 精确除法
 * @param scale 精度
 */
public static double div(double value1, double value2, int scale) throws IllegalAccessException {
    if(scale < 0) {
        throw new IllegalAccessException("精确度不能小于0");
    }
    BigDecimal b1 = BigDecimal.valueOf(value1);
    BigDecimal b2 = BigDecimal.valueOf(value2);
    // return b1.divide(b2, scale).doubleValue();
    return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}

/**
 * 四舍五入
 * @param scale 小數點後保留幾位
 */
public static double round(double v, int scale) throws IllegalAccessException {
    return div(v, 1, scale);
}

/**
 * 比較大小
 */
public static boolean equalTo(BigDecimal b1, BigDecimal b2) {
    if(b1 == null || b2 == null) {
        return false;
    }
    return 0 == b1.compareTo(b2);
}           

}

原文位址

https://www.cnblogs.com/wunsiang/p/12811661.html