天天看點

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

無論在什麼業務中,錢?是非常重要的東西,對賬的時候一定要對的上,不能這邊少一分錢那邊多一分錢。對于數值的計算,尤其是小數,

floate

double

都是禁止使用的。

阿裡強制要求存放小數時使用 decimal,禁止使用 float 和 double。

說明:float 和 double 在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不正确的結果。如果存儲的資料範圍超過 decimal 的範圍,建議将資料拆成整數和小數分開存儲。

處理方式可以為:

mysql

 可以用 

decimal

 ,如果你是用 

java

, 在商業計算中我們要用 

java.math.BigDecimal

,注意:如果需要精确計算,非要用

String

來構造

BigDecimal

不可!

那麼到底是什麼情況?為什麼我們的賬戶一會少一分一會多一分(往往是少一分

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

),如何解決呢?

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

一個例子說明

廢話不多說,當我們拿着一塊錢去買了一根9毛的棒冰會發生啥?本來隻剩1毛錢就不多了,老闆還扣我0.000....0002分?上圖:

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

問題原因

無論是我們本文提到的

double

,還是

float

,都是浮點數。

在計算機科學中,浮點(英語:

floating point

,縮寫為FP)是一種對于實數的近似值數值表現法,由一個有效數字(即尾數)加上幂數來表示,通常是乘以某個基數的整數次指數得到。以這種表示法表示的數值,稱為浮點數(

floating-point number

)。

劃重點,⭐⭐⭐其實我覺得很好了解,我們之前說過,計算機計算加減乘除啊,都是用的加法器,實質都是二進制的加法處理。那麼這裡就有一個二進制表示的問題。試想,4,2,8之流都是2的幂次方,可以完美用二進制表示,計算當然不會出現問題。對于0,1,3,5之類也都可以用二進制來表示出來,是以,整數肯定是沒問題的。

但是對于小數呢?1(2的0次方)、0.5(2的-1次方)、0.25(2的-2次方)、0.75(2的-1次方+2的-2次方),那都是可以轉換成二進制的小數:

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

但是如十進制的0.1,就無法用二進制準确的表示出來(你用2的次方來湊湊?)。是以隻能使用近似值的方式表達。如果我們嘗試着把10進制的0.1轉化成二進制,會怎麼轉呢?

在十進制中,0.1如何計算出來的呢?

0.1 = 1 ÷ 10

那麼二進制中也是同理:

1 ÷ 1010

我們回到國小的課堂,來列豎式吧:

很顯然,除不盡,除出了一個無限循環小數:二進制的 0.0001100110011...

有的同學表示懷疑?這結果正确?

我寫在這裡當然正确啦,前面标注了是二進制,小數點後面一位就是-1次方依次計算,我們的0.1是不是介于(2的-3次方)和(2的-4次方)之間,那麼顯然是從小數點第四個開始有1。

好了,那麼,如何在計算機中表示這個無限不循環的小數呢?隻能考慮按照不同的精度保留不同的位數。

我們知道

float

是單精度的(JAVA中是32位),

double

是雙精度的(JAVA中是64位)。不同的精度,其實就是保留的有效數字位數不同,保留的位數越多,精度越高。

是以,浮點數在Java中是無法精确表示的,因為大部分浮點數轉換成二進制是一個無限不循環的小數,隻能通過保留精度的方式進行近似表示。

問題的解決

String

 構造方法是完全可預知的:寫入 

newBigDecimal("0.1")

 将建立一個 

BigDecimal

,它正好等于預期的 0.1。是以,比較而言,通常建議優先使用

String

構造方法。

使用

BigDecimal(String val)

那麼,上面的精度丢失問題就迎刃而解了。但是除不盡怎麼辦?比如10.0除以這裡的3.0,保留小數點後三位有效數字:

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

那麼,每個使用者得到的都是3.333元,三個使用者加起來是得不到10塊錢的。

對于除法,始終會産生除不盡的情況怎麼辦?有個詞叫軋差

什麼意思呢?舉個簡單例子。假如現在需要把10元分成3分,如果是10除以3這麼除,會發現為3.33333無窮盡的3。這些數字完全無法在程式或資料庫中進行精确的存儲。

簡單了解就是,當除不盡或需去除小數點的時候,前面的n-1筆(這裡n=3)做四舍五入。最後一筆做兜底(總金額減去前面n-1筆之和)。這樣保證總金額的不會丢失。

比如10塊錢,三個使用者分,前面兩個使用者隻能各分到3.333塊錢,最後一個使用者分到3.334塊錢。保證總額不變。是不是感覺很機智

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決
bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決
bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

好了,我們可以準确地管理我們的錢了。不過針對這個錢的問題,更機智的我選擇儲存錢的時候用分為機關來操作和儲存

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決
bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決
bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

,能少一事就少一事吧。當然了,有的時候必須用到小數的時候,請記住我們該如何使用哦

bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決
bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決
bigdecimal加減乘除運算保留兩位小數_小數精度丢失問題分析和解決

~

繼續閱讀