Java BigDecimal 使用避坑指南

踩坑一:創(chuàng)建時精度丟失

在BigDecimal 中提供了多種創(chuàng)建方式稽荧,可以通過new 直接創(chuàng)建橘茉,也可以通過 valueOf 創(chuàng)建。這兩種方式使用不當(dāng),也會導(dǎo)致精度問題畅卓。如下:

public static void main(String[] args) throws Exception {
   BigDecimal b1= new BigDecimal(0.1);
   System.out.println(b1);
   BigDecimal b2= BigDecimal.valueOf(0.1);
   System.out.println(b2);
   BigDecimal b3= BigDecimal.valueOf(0.111111111111111111111111111234);
   System.out.println(b3);
}

執(zhí)行結(jié)果:

0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111

上面示例中兩個方法都傳入了double 類型的參數(shù) 0.1擅腰,但是 b1 還是出現(xiàn)了精度的問題。
造成這種問題的原因是 0.1 這個數(shù)字計算機(jī)是無法精確表示的髓介,
給 BigDecimal 的時候就已經(jīng)丟精度了惕鼓,而BigDecimal#valueOf 的實(shí)現(xiàn)卻完全不同筋现。

如下源碼所示唐础,BigDecimal# valueOf 中是 先把把浮點(diǎn)數(shù)轉(zhuǎn)換成了字符串 來構(gòu)造的BigDecimal,因此避免了問題矾飞。

public static BigDecimal valueOf(double val) {
   return new BigDecimal(Double.toString(val));
}

結(jié)論:
1> 使用 BigDecimal 構(gòu)造函數(shù)時一膨,盡量傳遞 字符串 而非 浮點(diǎn)類型;
2> 如果無法滿足第一條洒沦,則可采用 BigDecimal#valueOf 方法來構(gòu)造初始化值豹绪。但 valueOf 受 double 類型精度影響,當(dāng)傳入?yún)?shù)小數(shù)點(diǎn)后的位數(shù) 超過 double 允許的16位精度申眼,還是可能會出現(xiàn)問題的瞒津。

踩坑二:等值比較的坑

一般在比較兩個值是否相等時,都是用equals 方法括尸,但是巷蚪,在BigDecimal 中使用equals可能會導(dǎo)致結(jié)果錯誤,BigDecimal 中提供了 compareTo 方法濒翻,在很多時候需要使用compareTo 比較兩個值屁柏。如下所示:

public static void main(String[] args){
    BigDecimal b1 = new BigDecimal("1.0");
    BigDecimal b2 = new BigDecimal("1.00");
    System.out.println(b1.equals(b2));
    System.out.println(b1.compareTo(b2));
}

執(zhí)行結(jié)果:

false
0

原因是
1> equals不僅比較了值是否相等,還比較了精度是否相同有送。
示例中淌喻,由于兩個值的精度不同,所有結(jié)果也就不相同雀摘。
2> compareTo 是只比較值的大小裸删。返回的值為 -1(小于),0(等于)阵赠,1(大于)涯塔。

結(jié)論:
如果比較兩個 BigDecimal 值大小,采用 compareTo 方法豌注;
如果 嚴(yán)格限制精度的比較伤塌,那么則可考慮使用 equals 方法。

踩坑三:無限精度

BigDecimal 并不代表無限精度轧铁,當(dāng)在兩個數(shù)除不盡的時候每聪,就會出現(xiàn)無限精度的坑,如下所示:

 public static void main(String[] args){
   BigDecimal b1 = new BigDecimal("1.0");
    BigDecimal b2 = new BigDecimal("3.0");
    b1.divide(b2);
}

執(zhí)行結(jié)果:


無限精度

在官方文檔中對該異常有如下說明:

If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.

大致意思就是,如果在除法(divide)運(yùn)算過程中药薯,如果商是一個無限小數(shù)(如 0.333…)绑洛,而操作的結(jié)果預(yù)期是一個精確的數(shù)字,那么將會拋出 ArithmeticException 異常童本。

此種情況真屯,只需要在使用 divide方法時指定結(jié)果的精度即可:

public static void main(String[] args){
   BigDecimal b1 = new BigDecimal("1.0");
   BigDecimal b2 = new BigDecimal("3.0");
   System.out.println(b1.divide(b2,2, RoundingMode.HALF_UP));  //0.33
}

結(jié)論:
在使用BigDecimal進(jìn)行(所有)運(yùn)算時,盡量指定精度 和 舍入模式穷娱。

踩坑四:BigDecimal 三種字符串輸出

在 BigDecimal 轉(zhuǎn)換成字符串時绑蔫,有可能輸出非你預(yù)期的結(jié)果。
如下所示:

public static void main(String[] args){
   BigDecimal bg = new BigDecimal("1E11");
    System.out.println(bg.toString()); // 1E+11
    System.out.println(bg.toPlainString()); // 100000000000
    System.out.println(bg.toEngineeringString()); // 100E+9
}

執(zhí)行結(jié)果:

1E+11
100000000000
100E+9

下面介紹 java.math.BigDecimal 下的三個toString方法的區(qū)別及用法

  • toString() :有必要時使用科學(xué)計數(shù)法泵额。
  • toPlainString() : 不使用任何指數(shù)配深。
  • toEngineeringString():有必要時使用工程計數(shù)法。

工程記數(shù)法 是一種 工程計算 中經(jīng)常使用的記錄數(shù)字的方法嫁盲,與科學(xué)技術(shù)法類似篓叶,但要求 10的冪必須是3的倍數(shù)

踩坑五:使用BigDecimal進(jìn)行計算時參數(shù)不能為NULL

在使用BigDecimal 進(jìn)行加羞秤、減缸托、乘、除瘾蛋、比較大小時俐镐,
要保證參與計算的兩個值不能為空,否則會有 NPE 瘦黑。

代碼示例:

BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = null;
System.out.println("相加:" + b2.add(b1));

結(jié)果:


空指針異常

踩坑六:除法計算時 除數(shù)不能為0

代碼示例:

BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = new BigDecimal("0");
System.out.println(b1.divide(b2));

執(zhí)行結(jié)果:


除數(shù)不能為0

踩坑七:執(zhí)行順序不能調(diào)換(乘法交換律失效)

乘法滿足交換律是一個常識京革,但是在計算機(jī)的世界里,會出現(xiàn)不滿足乘法交換律的情況幸斥;

代碼示例:

        BigDecimal b1 = BigDecimal.valueOf(1.0);
        BigDecimal b2 = BigDecimal.valueOf(3.0);
        BigDecimal b3 = BigDecimal.valueOf(3.0);
        // b1 / b2 * b3
        System.out.println(b1.divide(b2, 2, RoundingMode.HALF_UP).multiply(b3)); // 0.990
        // b1 * b3 / b2
        System.out.println(b1.multiply(b3).divide(b2, 2, RoundingMode.HALF_UP)); // 1.00

執(zhí)行結(jié)果:

0.990
1.00

執(zhí)行順序交換后匹摇,產(chǎn)生的結(jié)果可能不同,會導(dǎo)致一定的問題甲葬,使用順序建議先乘后除廊勃。

文章參考

Java BigDecimal 詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市经窖,隨后出現(xiàn)的幾起案子坡垫,更是在濱河造成了極大的恐慌,老刑警劉巖画侣,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冰悠,死亡現(xiàn)場離奇詭異,居然都是意外死亡配乱,警方通過查閱死者的電腦和手機(jī)溉卓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門皮迟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人桑寨,你說我怎么就攤上這事伏尼。” “怎么了尉尾?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵爆阶,是天一觀的道長。 經(jīng)常有香客問我沙咏,道長辨图,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任芭碍,我火速辦了婚禮徒役,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窖壕。我一直安慰自己,他們只是感情好杉女,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布瞻讽。 她就那樣靜靜地躺著,像睡著了一般熏挎。 火紅的嫁衣襯著肌膚如雪速勇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天坎拐,我揣著相機(jī)與錄音烦磁,去河邊找鬼。 笑死哼勇,一個胖子當(dāng)著我的面吹牛都伪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播积担,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼陨晶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了帝璧?” 一聲冷哼從身側(cè)響起先誉,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎的烁,沒想到半個月后褐耳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渴庆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年铃芦,在試婚紗的時候發(fā)現(xiàn)自己被綠了买雾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡杨帽,死狀恐怖漓穿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情注盈,我是刑警寧澤晃危,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站老客,受9級特大地震影響僚饭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胧砰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一鳍鸵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尉间,春花似錦偿乖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至眠副,卻和暖如春画切,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背囱怕。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工霍弹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娃弓。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓典格,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忘闻。 傳聞我的和親對象是個殘疾皇子钝计,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容