bigdecimal取小数部分_小数精度丢失问题分析和解决
無論在什么業(yè)務中,錢?是非常重要的東西,對賬的時候一定要對的上,不能這邊少一分錢那邊多一分錢。對于數值的計算,尤其是小數,floate和double都是禁止使用的。
阿里強制要求存放小數時使用 decimal,禁止使用 float 和 double。
說明:float 和 double 在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不正確的結果。如果存儲的數據范圍超過 decimal 的范圍,建議將數據拆成整數和小數分開存儲。
處理方式可以為:mysql?可以用?decimal?,如果你是用?java, 在商業(yè)計算中我們要用?java.math.BigDecimal,注意:如果需要精確計算,非要用String來構造BigDecimal不可!
那么到底是什么情況?為什么我們的賬戶一會少一分一會多一分(往往是少一分),如何解決呢?
一個例子說明
廢話不多說,當我們拿著一塊錢去買了一根9毛的棒冰會發(fā)生啥?本來只剩1毛錢就不多了,老板還扣我0.000....0002分?上圖:
問題原因
無論是我們本文提到的double,還是float,都是浮點數。
在計算機科學中,浮點(英語:floating point,縮寫為FP)是一種對于實數的近似值數值表現(xiàn)法,由一個有效數字(即尾數)加上冪數來表示,通常是乘以某個基數的整數次指數得到。以這種表示法表示的數值,稱為浮點數(floating-point number)。
劃重點,???其實我覺得很好理解,我們之前說過,計算機計算加減乘除啊,都是用的加法器,實質都是二進制的加法處理。那么這里就有一個二進制表示的問題。試想,4,2,8之流都是2的冪次方,可以完美用二進制表示,計算當然不會出現(xiàn)問題。對于0,1,3,5之類也都可以用二進制來表示出來,所以,整數肯定是沒問題的。
但是對于小數呢?1(2的0次方)、0.5(2的-1次方)、0.25(2的-2次方)、0.75(2的-1次方+2的-2次方),那都是可以轉換成二進制的小數:
但是如十進制的0.1,就無法用二進制準確的表示出來(你用2的次方來湊湊?)。因此只能使用近似值的方式表達。如果我們嘗試著把10進制的0.1轉化成二進制,會怎么轉呢?
在十進制中,0.1如何計算出來的呢?
0.1 = 1 ÷ 10
那么二進制中也是同理:
1 ÷ 1010
我們回到小學的課堂,來列豎式吧:
0.000110011...------------------
1010 ) 1 0000
1010
------
1100
1010
----
10000
1010
-----
1100
1010
----
10
很顯然,除不盡,除出了一個無限循環(huán)小數:二進制的 0.0001100110011...
有的同學表示懷疑?這結果正確?
我寫在這里當然正確啦,前面標注了是二進制,小數點后面一位就是-1次方依次計算,我們的0.1是不是介于(2的-3次方)和(2的-4次方)之間,那么顯然是從小數點第四個開始有1。
好了,那么,如何在計算機中表示這個無限不循環(huán)的小數呢?只能考慮按照不同的精度保留不同的位數。
我們知道float是單精度的(JAVA中是32位),double是雙精度的(JAVA中是64位)。不同的精度,其實就是保留的有效數字位數不同,保留的位數越多,精度越高。
所以,浮點數在Java中是無法精確表示的,因為大部分浮點數轉換成二進制是一個無限不循環(huán)的小數,只能通過保留精度的方式進行近似表示。
問題的解決
String?構造方法是完全可預知的:寫入?newBigDecimal("0.1")?將創(chuàng)建一個?BigDecimal,它正好等于預期的 0.1。因此,比較而言,通常建議優(yōu)先使用String構造方法。
使用BigDecimal(String val)!
//加法public static BigDecimal add(double v1, double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
//減法
public static BigDecimal sub(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
//乘法
public static BigDecimal mul(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
//除法
public static BigDecimal div(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留2位小數,應對除不盡的情況
}
那么,上面的精度丟失問題就迎刃而解了。但是除不盡怎么辦?比如10.0除以這里的3.0,保留小數點后三位有效數字:
那么,每個用戶得到的都是3.333元,三個用戶加起來是得不到10塊錢的。
對于除法,始終會產生除不盡的情況怎么辦?有個詞叫軋差
什么意思呢?舉個簡單例子。假如現(xiàn)在需要把10元分成3分,如果是10除以3這么除,會發(fā)現(xiàn)為3.33333無窮盡的3。這些數字完全無法在程序或數據庫中進行精確的存儲。
簡單理解就是,當除不盡或需去除小數點的時候,前面的n-1筆(這里n=3)做四舍五入。最后一筆做兜底(總金額減去前面n-1筆之和)。這樣保證總金額的不會丟失。
比如10塊錢,三個用戶分,前面兩個用戶只能各分到3.333塊錢,最后一個用戶分到3.334塊錢。保證總額不變。是不是感覺很機智
好了,我們可以準確地管理我們的錢了。不過針對這個錢的問題,更機智的我選擇保存錢的時候用分為單位來操作和保存,能少一事就少一事吧。當然了,有的時候必須用到小數的時候,請記住我們該如何使用哦~
總結
以上是生活随笔為你收集整理的bigdecimal取小数部分_小数精度丢失问题分析和解决的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js 取得数组下标_数组的介绍及使用
- 下一篇: 有什么用_app用什么软件编写