天天看點

BigDecimal的精度問題以及解決方案背景場景分析解決

背景

我們在開發産品時, 會遇到金額的問題. 與普通的計算不同, 涉及到錢的問題不馬虎, 多一分少一分肯定是不行的.

資料庫: 存儲金額時, bigint, 以分為機關.

場景

在支付一個訂單時, 企業支付一部分, 個人支付一部分.

如果發生退單并且産生手續費的情況, 這個手續費是針對這個訂單的.

是以企業和個人都需要承擔一部分手續費, 然後再退款到企業和個人賬戶上.

而計算手續費的比例, 必然要用到除法運算.

比如: 先算企業手續費:

``

企業手續費 = 手續費 * (企業支付金額 / 訂單總金額);

comFee = fee * ( comPayAmount / payAmount).

Long = Long * (BigDecimal / BigDecimal)

comFee = fee * comPercent;

Long = Long * BigDecimal

``

個人手續費就是個減法.

而精度問題會出現在兩個地方:

  1. 計算百分比
  2. BigDecimal轉long

分析

  1. 使用float
    float compPercent = (float) companyPayAmount / (float) payAmount;
    long companyFee = (long) ((float) penaltyAmount * (compPercent));
               
    這種方式, 數字稍微大一點, 就會有精度問題, 會出現多一分少一分的情況.
  2. 使用BigDecimal
    BigDecimal compPercent = BigDecimal.valueOf(companyPayAmount).divide(BigDecimal.valueOf(payAmount));
    long companyFee =  BigDecimal.valueOf(penaltyAmount).multiply(compPercent).longValue();
               
    使用這種方式, 有兩個問題:

    divide()

    除不盡會報錯,

    longValue()

    會出現舍棄小數點後的資料,結果不準确.

解決

BigDecimal compPercent = BigDecimal.valueOf(companyPayAmount).divide(BigDecimal.valueOf(payAmount), 20, BigDecimal.ROUND_HALF_EVEN);
long companyFee =  BigDecimal.valueOf(penaltyAmount).multiply(compPercent).round(MathContext.DECIMAL64).longValue();
           
  1. divide

    可以指定: 保留多少位小數, 我這裡取20位; 舍入的方式, 我這裡取4舍5入.
  2. 轉化為long類型時, 需要先做一次舍入, 再轉成long型.

    round(MathContext.DECIMAL64).longValue();