Java?BigDecimal正確用法詳解
一、背景
BigDecimal 平時主要用于計算金錢時,其自身提供了很多的構(gòu)造方法,但是這些構(gòu)造方法使用不當會造成精度丟失,從而引起事故。
二、事故案例
1、問題
收銀臺計算商品價格報錯,導致訂單無法支付
2、問題復現(xiàn)
public static void main(String[] args) {
BigDecimal bigDecimal=new BigDecimal(88);
System.out.println(bigDecimal);
bigDecimal=new BigDecimal("8.8");
System.out.println(bigDecimal);
bigDecimal=new BigDecimal(8.8);
System.out.println(bigDecimal);
}

3、源碼分析
public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
result = 0x7ff8000000000000L;
return result;
}
問題就處在 doubleToRawLongBits 這個方法上,在 jdk 中 double 類(float 與 int 對應)中提供了 double 與 long 轉(zhuǎn)換,doubleToRawLongBits 就是將 double 轉(zhuǎn)換為 long,這個方法是原始方法(底層不是 java 實現(xiàn),是 c++ 實現(xiàn)的)。
4、原因分析
在 java 中 BigDecimal 處理數(shù)據(jù)時把十進制小數(shù)擴大 N 倍讓它在整數(shù)上進行計算,并保留相應的精度信息。
- float 和 double 類型,主要是為了科學計算和工程計算而設計的,之所以執(zhí)行二進制浮點運算,是為了在廣泛的數(shù)值范圍上提供較為精確的快速近和計算。
- 并沒有提供完全精確的結(jié)果,所以不應該被用于精確的結(jié)果的場合。
- 當浮點數(shù)達到一定大的數(shù),就會自動使用科學計數(shù)法,這樣的表示只是近似真實數(shù)而不等于真實數(shù)。
- 當十進制小數(shù)位轉(zhuǎn)換二進制的時候也會出現(xiàn)無限循環(huán)或者超過浮點數(shù)尾數(shù)的長度。
三、總結(jié)
在設計到精度計算時,我們盡量使用 String 類型來進行轉(zhuǎn)換,而且涉及到 BigDecimal 的計算,要使用其對應方法進行計算。
四、工具類
這里封裝一個 BigDecimal 工具類
public class BigDecimalUtils {
/**
* double 加
*
* @param v1 加數(shù)
* @param v2 加數(shù)
* @return 和
*/
public static BigDecimal doubleAdd(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
/**
* float 加
*
* @param v1 加數(shù)
* @param v2 加數(shù)
* @return 和
*/
public static BigDecimal floatAdd(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.add(b2);
}
/**
* double 減
*
* @param v1 被減數(shù)
* @param v2 減數(shù)
* @return 差
*/
public static BigDecimal doubleSub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
/**
* float 減
*
* @param v1 被減數(shù)
* @param v2 減數(shù)
* @return 差
*/
public static BigDecimal floatSub(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.subtract(b2);
}
/**
* double 乘
*
* @param v1 因數(shù)
* @param v2 因數(shù)
* @return 積
*/
public static BigDecimal doubleMul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
/**
* float 乘
*
* @param v1 因數(shù)
* @param v2 因數(shù)
* @return 積
*/
public static BigDecimal floatMul(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.multiply(b2);
}
/**
* double 除
*
* @param v1 被除數(shù)
* @param v2 除數(shù)
* @return 商
*/
public static BigDecimal doubleDiv(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
// 保留小數(shù)點后兩位 ROUND_HALF_UP = 四舍五入
return b1.divide(b2, 2, RoundingMode.HALF_UP);
}
/**
* float 除
*
* @param v1 被除數(shù)
* @param v2 除數(shù)
* @return 商
*/
public static BigDecimal floatDiv(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
// 保留小數(shù)點后兩位 ROUND_HALF_UP = 四舍五入
return b1.divide(b2, 2, RoundingMode.HALF_UP);
}
/**
* double<br>
* 比較v1 v2大小
*
* @param v1
* @param v2
* @return v1>v2 return 1 v1=v2 return 0 v1<v2 return -1
*/
public static int doubleCompareTo(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.compareTo(b2);
}
/**
* float<br>
* 比較v1 v2大小
*
* @param v1
* @param v2
* @return v1>v2 return 1 v1=v2 return 0 v1<v2 return -1
*/
public static int floatCompareTo(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.compareTo(b2);
}
}到此這篇關(guān)于Java BigDecimal正確用法詳解的文章就介紹到這了,更多相關(guān)Java BigDecimal內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis 訂閱發(fā)布_Jedis實現(xiàn)方法
下面小編就為大家?guī)硪黄猂edis 訂閱發(fā)布_Jedis實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06
繼承WebMvcConfigurationSupport后自動配置不生效及如何配置攔截器
這篇文章主要介紹了繼承WebMvcConfigurationSupport后自動配置不生效及如何配置攔截器,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11
基于SpringBoot實現(xiàn)Web應用的登錄與退出功能
登錄與退出功能作為 Web 應用中的基礎且重要的組成部分,直接關(guān)系到用戶的安全和隱私保護,所以本文給大家介紹了基于SpringBoot實現(xiàn)Web應用的登錄與退出功能,文中有詳細的代碼供大家參考,需要的朋友可以參考下2024-04-04
SpringBoot啟動并初始化執(zhí)行sql腳本問題
這篇文章主要介紹了SpringBoot啟動并初始化執(zhí)行sql腳本問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
Java實現(xiàn)ATM銀行管理系統(tǒng)(控制臺版本)
這篇文章主要為大家詳細介紹了如何利用Java語言實現(xiàn)控制臺版本的ATM銀行管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06

