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