java中精確計(jì)算则拷,double與BigDecimal的取舍

相信java程序員都知道double是一種不能用作精確計(jì)算的類型,因?yàn)樗鼤?huì)有精度損失曹鸠,而要想規(guī)避精度損失煌茬,大家都會(huì)想到BigDecimal,這是JDK提供的類彻桃,確實(shí)能解決精度問題坛善,但是它并不是完美的,它有如下三個(gè)缺點(diǎn):

  • 也不是那么準(zhǔn)確

一邻眷、慢有多慢眠屎?
所有人都知道,BigDecimal作為對(duì)象肆饶,new出來是有成本的改衩,肯定比基本數(shù)據(jù)類型會(huì)慢一點(diǎn),但具體慢到什么程度呢驯镊?我寫了一段求加和的代碼如下:

        double sum = 0.0;
        long a = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {
            double d = Math.random();
            sum += d;
        }
        long b = System.currentTimeMillis();
        System.out.println(b - a);
        System.out.println(sum);
        Double sum = 0.0;
        long a = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {
            Double d = Math.random();
            sum += d;
        }
        long b = System.currentTimeMillis();
        System.out.println(b - a);
        System.out.println(sum);
        BigDecimal sum = new BigDecimal("0");
        long a = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {
            BigDecimal d = new BigDecimal(Math.random());
            sum = sum.add(d);
        }
        long b = System.currentTimeMillis();
        System.out.println(b - a);
        System.out.println(sum);

這三段代碼分別是用基本數(shù)據(jù)類型葫督、包裝數(shù)據(jù)類型和BigDecimal來計(jì)算一個(gè)和,我的電腦上分別運(yùn)行這三段代碼板惑,當(dāng)times是10000的時(shí)候候衍,前兩個(gè)是1ms執(zhí)行時(shí)間,而第三個(gè)是30ms洒放,當(dāng)times是100000000的時(shí)候,前兩個(gè)是差不多2秒(但是結(jié)果顯然不正確)滨砍,而第三個(gè)是差不多40秒往湿,這是20~30倍的執(zhí)行效率差距,所以BigDecimal絕不能濫用惋戏。至于為什么包裝類的創(chuàng)建幾乎沒有感覺到呢领追?自動(dòng)拆裝箱不需要時(shí)間嗎?這個(gè)就涉及到一些底層的優(yōu)化策略了响逢,在此不做深究绒窑,先了解一下結(jié)論,不用太糾結(jié)基本類型和包裝類型舔亭。
二些膨、亂又怎樣?
亂钦铺,在某些人看來订雾,并不能稱為問題,但我是有些不能接受的矛洞,比如一個(gè)簡(jiǎn)單的物理公式:距離=速度時(shí)間+(加速度時(shí)間的平方)/2(即:S=vt+1/2at^2)洼哎,用double來寫就跟公式本身很像,而用BigDecimal,即使是這樣簡(jiǎn)單的公式噩峦,也讓人看得云里霧里锭沟,不知所云,代碼如下:

        double s;
        double v = 2323.346852;
        double a = 102.1523684;
        double t = 20.004;
        s = v * t + (a * Math.pow(t, 2)) / 2;
        System.out.println(s); // 66914.87711409896
        BigDecimal s;
        BigDecimal v = new BigDecimal(2323.346852);
        BigDecimal a = new BigDecimal(102.1523684);
        BigDecimal t = new BigDecimal(20.004);
        // 寫這個(gè)可得留神识补,稍微一個(gè)不注意族淮,錯(cuò)個(gè)括號(hào),那可就差大了李请,
        // 有的人可能想要提取變量瞧筛,可能會(huì)好點(diǎn),但很多時(shí)候就像這個(gè)例子导盅,
        // 最復(fù)雜的部分a*t^2/2较幌,提取出來的變量叫什么名能夠見名知意?可能效果跟不提取也沒啥區(qū)別白翻。
        s = v.multiply(t).add(a.multiply(t.pow(2)).divide(new BigDecimal(2)));
        System.out.println(s); // 66914.8771140989556179216886351698979346...........還沒完

三乍炉、JDK的類能不準(zhǔn)?
關(guān)于BigDecimal計(jì)算不準(zhǔn)確的問題滤馍,項(xiàng)目中已經(jīng)多次遇到了岛琼,分為兩方面,一方面是BigDecimal的用法不對(duì)巢株,本文中上面所列舉的所有關(guān)于BigDecimal的代碼槐瑞,用法都是錯(cuò)的;另一方面是即使用對(duì)了結(jié)果也未必是你想要的阁苞。
先說第一個(gè)用法的問題困檩,很多程序員在使用BigDecimal時(shí)會(huì)用BigDecimal(double val)這個(gè)構(gòu)造方法,JDK的文檔中說得非常明白“The results of this constructor can be somewhat unpredictable”那槽,“The String constructor, on the other hand, is perfectly predictable”(double的構(gòu)造方法是垃圾悼沿,String的構(gòu)造方法是完美的),而很少有人在被這個(gè)構(gòu)造方法坑掉之前查閱這個(gè)文檔骚灸,我們用String構(gòu)造重新執(zhí)行一下算距離的公式

        BigDecimal s;
        BigDecimal v = new BigDecimal("2323.346852");
        BigDecimal a = new BigDecimal("102.1523684");
        BigDecimal t = new BigDecimal("20.004");
        s = v.multiply(t).add(a.multiply(t.pow(2)).divide(new BigDecimal("2")));
        System.out.println(s); // 66914.8771140989472(完結(jié))

可以看出糟趾,用double構(gòu)造方法來計(jì)算,精度幾乎沒有比double提升多少甚牲,但開銷多了30倍义郑。可見對(duì)知識(shí)的一知半解有的時(shí)候比完全不懂更差丈钙。
有的時(shí)候魔慷,現(xiàn)實(shí)是很殘酷的,我們用了正確的方法使用了BigDecimal而結(jié)果依然可能是不盡如人意的著恩,比如下面這個(gè)場(chǎng)景:

部門 人員 考勤(分鐘)
1 Jack 3073.32
1 Robin 3073.32
1 小白 3073.32
2 Robin 3073.32
2 小白 3073.32
2 Jack 3073.32

假設(shè)某個(gè)公司有三個(gè)人院尔,兩個(gè)部門蜻展,而這三個(gè)人來回在這兩個(gè)部門之間調(diào)動(dòng),現(xiàn)在要統(tǒng)計(jì)每個(gè)人的總考勤邀摆,每個(gè)部門的總考勤纵顾,還有整個(gè)公司的總考勤,并且是要以小時(shí)為單位栋盹,保留兩位小數(shù)施逾,這個(gè)需求一看就很明確,每個(gè)人的考勤總和例获、每個(gè)部門的考勤總和汉额、公司的總考勤應(yīng)該是相等的,但不幸的是榨汤,經(jīng)過計(jì)算可能會(huì)得到下面的結(jié)果:

部門 考勤(分鐘) 考勤(小時(shí)) 人員 考勤(分鐘) 考勤(小時(shí))
1 9219.96 153.67 Jack 6146.64 102.44
2 9219.96 153.67 Robin 6146.64 102.44
- - - 小白 6146.64 102.44
合計(jì) - 307.34 合計(jì) - 307.32

顯然蠕搜,每個(gè)人的考勤總和、每個(gè)部門的考勤總和已經(jīng)不相等了收壕,而這跟你用double還是BigDecimal無關(guān)妓灌。更令人絕望的是整個(gè)公司的合計(jì)是18439.92分鐘,保留小數(shù)后是307.33小時(shí)蜜宪,三個(gè)本應(yīng)該相等的數(shù)虫埂,全都不相等。
可能有的朋友已經(jīng)看出了端倪圃验,這不是一個(gè)技術(shù)問題掉伏,這已經(jīng)是一個(gè)業(yè)務(wù)了,如果這個(gè)項(xiàng)目的需求就是這樣的澳窑,那作為開發(fā)人員必須據(jù)理力爭(zhēng)斧散,痛陳這個(gè)不合理性,不要再做任何技術(shù)嘗試照捡,不管是用double還是BigDecimal都是在浪費(fèi)時(shí)間。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末话侧,一起剝皮案震驚了整個(gè)濱河市栗精,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞻鹏,老刑警劉巖悲立,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異新博,居然都是意外死亡薪夕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門赫悄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來原献,“玉大人馏慨,你說我怎么就攤上這事」糜纾” “怎么了写隶?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)讲仰。 經(jīng)常有香客問我慕趴,道長(zhǎng),這世上最難降的妖魔是什么鄙陡? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任冕房,我火速辦了婚禮,結(jié)果婚禮上趁矾,老公的妹妹穿的比我還像新娘耙册。我一直安慰自己,他們只是感情好愈魏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布觅玻。 她就那樣靜靜地躺著,像睡著了一般培漏。 火紅的嫁衣襯著肌膚如雪溪厘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天牌柄,我揣著相機(jī)與錄音畸悬,去河邊找鬼。 笑死珊佣,一個(gè)胖子當(dāng)著我的面吹牛蹋宦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咒锻,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冷冗,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了惑艇?” 一聲冷哼從身側(cè)響起蒿辙,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎滨巴,沒想到半個(gè)月后思灌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恭取,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年泰偿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜈垮。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耗跛,死狀恐怖裕照,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情课兄,我是刑警寧澤牍氛,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站烟阐,受9級(jí)特大地震影響搬俊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜒茄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一唉擂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧檀葛,春花似錦玩祟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至润讥,卻和暖如春转锈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背楚殿。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工撮慨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脆粥。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓砌溺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親变隔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子规伐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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