Java?BigDecimal正確用法詳解
一、背景
BigDecimal 平時(shí)主要用于計(jì)算金錢時(shí),其自身提供了很多的構(gòu)造方法,但是這些構(gòu)造方法使用不當(dāng)會(huì)造成精度丟失,從而引起事故。
二、事故案例
1、問題
收銀臺(tái)計(jì)算商品價(jià)格報(bào)錯(cuò),導(dǎo)致訂單無法支付
2、問題復(fù)現(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 這個(gè)方法上,在 jdk 中 double 類(float 與 int 對(duì)應(yīng))中提供了 double 與 long 轉(zhuǎn)換,doubleToRawLongBits 就是將 double 轉(zhuǎn)換為 long,這個(gè)方法是原始方法(底層不是 java 實(shí)現(xiàn),是 c++ 實(shí)現(xiàn)的)。
4、原因分析
在 java 中 BigDecimal 處理數(shù)據(jù)時(shí)把十進(jìn)制小數(shù)擴(kuò)大 N 倍讓它在整數(shù)上進(jìn)行計(jì)算,并保留相應(yīng)的精度信息。
- float 和 double 類型,主要是為了科學(xué)計(jì)算和工程計(jì)算而設(shè)計(jì)的,之所以執(zhí)行二進(jìn)制浮點(diǎn)運(yùn)算,是為了在廣泛的數(shù)值范圍上提供較為精確的快速近和計(jì)算。
- 并沒有提供完全精確的結(jié)果,所以不應(yīng)該被用于精確的結(jié)果的場合。
- 當(dāng)浮點(diǎn)數(shù)達(dá)到一定大的數(shù),就會(huì)自動(dòng)使用科學(xué)計(jì)數(shù)法,這樣的表示只是近似真實(shí)數(shù)而不等于真實(shí)數(shù)。
- 當(dāng)十進(jìn)制小數(shù)位轉(zhuǎn)換二進(jìn)制的時(shí)候也會(huì)出現(xiàn)無限循環(huán)或者超過浮點(diǎn)數(shù)尾數(shù)的長度。
三、總結(jié)
在設(shè)計(jì)到精度計(jì)算時(shí),我們盡量使用 String 類型來進(jìn)行轉(zhuǎn)換,而且涉及到 BigDecimal 的計(jì)算,要使用其對(duì)應(yīng)方法進(jìn)行計(jì)算。
四、工具類
這里封裝一個(gè) 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ù)點(diǎn)后兩位 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ù)點(diǎn)后兩位 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)文章
淺談JSON的數(shù)據(jù)交換、緩存問題和同步問題
這篇文章主要介紹了淺談JSON的數(shù)據(jù)交換、緩存問題和同步問題,具有一定借鑒價(jià)值,需要的朋友可以參考下2017-12-12解決RestTemplate反序列化嵌套對(duì)象的問題
這篇文章主要介紹了解決RestTemplate反序列化嵌套對(duì)象的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Java實(shí)現(xiàn)解析zip壓縮包并獲取文件內(nèi)容
這篇文章主要為大家詳細(xì)介紹了如何利用Java語言實(shí)現(xiàn)頁面上傳一個(gè)源碼壓縮包,后端將壓縮包解壓,并獲取每個(gè)文件中的內(nèi)容,感興趣的可以動(dòng)手嘗試一下2022-07-07java 直接調(diào)用python腳本,并傳遞參數(shù)代碼實(shí)例
這篇文章主要介紹了java調(diào)用python腳本傳遞參數(shù)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Java中List Set和Map之間的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java集合的主要分為三種類型set集,list列表,map映射,接下來通過本文給大家詳細(xì)介紹java中l(wèi)ist、Set和Map之間的區(qū)別,需要的的朋友參考下吧2017-05-05