項目中使用了 BigDecimal 差點被開除

背景

從事金融相關項目届吁,對BigDecimal應該是再熟悉不過了,也有很多人因為不知道绿鸣、不了解或使用不當導致資損事件發(fā)生疚沐。

所以,如果你從事金融相關項目潮模,或者你的項目中涉及到金額的計算亮蛔,那么你一定要花時間看看這篇文章,全面學習一下BigDecimal

概述

一般情況下擎厢,對于不需要準確計算精度的數字究流,可以直接使用FloatDouble處理,但是 FloatDouble 會導致精度丟失动遭。所以在需要精確計算結果的項目中芬探,則必須使用BigDecimal類來操作。雖然厘惦,BigDecimalFloatDouble 能夠保證精度問題偷仿,但是使用不當也會踩坑。

四個BigDecimal 中容易踩的坑

1、創(chuàng)建 BigDecimal 的坑

BigDecimal 中提供了多種創(chuàng)建方式炎疆,可以通過new 直接創(chuàng)建卡骂,也可以通過 BigDecimal#valueOf 創(chuàng)建。這兩種方式使用不當形入,也會導致精度問題全跨。如下:

    @Test
    public void test(){

        BigDecimal b1 = new BigDecimal(0.1);
        BigDecimal b2 = BigDecimal.valueOf(0.1);

        System.out.println("b1=" + b1);
        System.out.println("b2=" + b2);

    }

輸出結果:

b1=0.1000000000000000055511151231257827021181583404541015625
b2=0.1

上面示例中 b1 還是出現(xiàn)了精度的問題。造成這種問題的原因是 0.1 這個數字計算機是無法精確表示的亿遂,送給 BigDecimal 的時候就已經丟精度了浓若,而 BigDecimal#valueOf 的實現(xiàn)卻完全不同。如下源碼所示蛇数,BigDecimal#valueOf 中是把浮點數轉換成了字符串來構造的BigDecimal挪钓,因此避免了問題。

public static BigDecimal valueOf(double val) {
    // Reminder: a zero double returns '0.0', so we cannot fastpath
    // to use the constant ZERO.  This might be important enough to
    // justify a factory approach, a cache, or a few private
    // constants, later.
    return new BigDecimal(Double.toString(val));
}

結論
第一耳舅,在使用BigDecimal構造函數時碌上,盡量傳遞字符串而非浮點類型;
第二浦徊,如果無法滿足第一條馏予,則可采用BigDecimal#valueOf方法來構造初始化值。

2盔性、等值比較的坑

一般在比較兩個值是否相等時霞丧,都是用equals 方法,但是冕香,在BigDecimal 中使用equals可能會導致結果錯誤蛹尝,BigDecimal 中提供了 compareTo 方法,在很多時候需要使用compareTo比較兩個值悉尾。如下所示:

    @Test
    public void testEquals(){

        BigDecimal b1 = new BigDecimal("1.0");
        BigDecimal b2 = new BigDecimal("1.00");

        System.out.println(b1.equals(b2));
        System.out.println(b1.compareTo(b2));

    }

輸出結果:

false
0

出現(xiàn)此種結果的原因是突那,equals不僅比較了值相等,還比較了精度是否相同构眯。示例中陨收,由于兩個值的精度不同,所有結果也就不相同鸵赖。而 compareTo 是值比較值的大小。返回的值為-1(小于)拄衰,0(等于)它褪,1(大于)。

結論
如果比較兩個BigDecimal值的大小翘悉,采用其實現(xiàn)的compareTo方法茫打;
如果嚴格限制精度的比較,那么則可考慮使用equals方法。

3老赤、無限精度的坑

BigDecimal 并不代表無限精度轮洋,當在兩個數除不盡的時候惋戏,就會出現(xiàn)無限精度的坑党觅,如下所示:

    @Test
    public void testDivide(){
        BigDecimal b1 = new BigDecimal("1.0");
        BigDecimal b2 = new BigDecimal("3.0");

        b1.divide(b2);

    }

輸出結果:

java.lang.ArithmeticException: Non-terminating decimal expansion;
 no exact representable decimal result.

在官方文檔中有如下說明:

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)運算過程中册倒,如果商是一個無限小數(0.333…)订雾,而操作的結果預期是一個精確的數字始鱼,那么將會拋出ArithmeticException異常琅坡。

此種情況春叫,只需要在使用 divide方法時指定結果的精度即可:

    @Test
    public void testDivide(){
        BigDecimal b1 = new BigDecimal("1.0");
        BigDecimal b2 = new BigDecimal("3.0");

        BigDecimal divide = b1.divide(b2, 2, RoundingMode.HALF_UP);

        System.out.println(divide);

    }

結論
在使用BigDecimal進行(所有)運算時责鳍,盡量指定精度和舍入模式碾褂。

4、輸出字符串的坑

BigDecimal 轉換成字符串時历葛,有可能輸出非你預期的結果正塌。如下所示:

    @Test
    public void testString(){
        BigDecimal a = BigDecimal.valueOf(332334535345456700.12345634534534578901);
        System.out.println(a.toString());
    }

輸出結果:

3.323345353454567E+17

可以看到結果已經被轉換成了科學計數法,可能這個并不是預期的結果 恤溶,BigDecimal有三個方法可以轉為相應的字符串類型乓诽,切記不要用錯:

String toString();     // 有必要時使用科學計數法
String toPlainString();   // 不使用科學計數法
String toEngineeringString();  // 工程計算中經常使用的記錄數字的方法,與科學計數法類似宏娄,但要求10的冪必須是3的倍數

小結

在金融項目中问裕,一般都是推薦使用BigDecimal,來避免精度的丟失孵坚。但是BigDecimal使用不當也會踩坑粮宛。本章內容主要介紹了在使用BigDecimal時經常容易踩的坑。雖然某些場景下推薦使用BigDecimal卖宠,它能夠達到更好的精度巍杈,但性能相較于doublefloat,還是有一定的損失的扛伍,特別在處理龐大筷畦,復雜的運算時尤為明顯。故一般精度的計算沒必要使用BigDecimal刺洒。而必須使用時鳖宾,一定要規(guī)避上述的坑。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末逆航,一起剝皮案震驚了整個濱河市鼎文,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌因俐,老刑警劉巖拇惋,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件周偎,死亡現(xiàn)場離奇詭異,居然都是意外死亡撑帖,警方通過查閱死者的電腦和手機蓉坎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胡嘿,“玉大人蛉艾,你說我怎么就攤上這事≡钇剑” “怎么了伺通?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逢享。 經常有香客問我罐监,道長,這世上最難降的妖魔是什么瞒爬? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任弓柱,我火速辦了婚禮,結果婚禮上侧但,老公的妹妹穿的比我還像新娘矢空。我一直安慰自己,他們只是感情好禀横,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布屁药。 她就那樣靜靜地躺著,像睡著了一般柏锄。 火紅的嫁衣襯著肌膚如雪酿箭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天趾娃,我揣著相機與錄音缭嫡,去河邊找鬼。 笑死抬闷,一個胖子當著我的面吹牛妇蛀,可吹牛的內容都是我干的。 我是一名探鬼主播笤成,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼评架,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炕泳?” 一聲冷哼從身側響起古程,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喊崖,沒想到半個月后挣磨,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡荤懂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年茁裙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片节仿。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡晤锥,死狀恐怖,靈堂內的尸體忽然破棺而出廊宪,到底是詐尸還是另有隱情矾瘾,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布箭启,位于F島的核電站壕翩,受9級特大地震影響,放射性物質發(fā)生泄漏傅寡。R本人自食惡果不足惜放妈,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荐操。 院中可真熱鬧芜抒,春花似錦、人聲如沸托启。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屯耸。三九已至拐迁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肩民,已是汗流浹背唠亚。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留持痰,地道東北人灶搜。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像工窍,于是被迫代替她去往敵國和親割卖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內容