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
0.1
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);
}
}