相信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í)間。