天天看點

阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題

4 浮點數的舍入和格式化

應考慮顯式編碼,通過格式化表達式或格式化工具

4.1 明确小數位數和舍入方式

  • 通過String.format使用

    %.1f

    格式化double/float的3.35浮點數
  • 阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題
  • 結果

    3.4和3.3

精度問題和舍入方式共同導緻:double/float的3.35實際存儲表示

3.350000000000000088817841970012523233890533447265625
3.349999904632568359375      

String.format采用四舍五入的方式進行舍入,取1位小數,double的3.350四舍五入為3.4,而float的3.349四舍五入為3.3。

我們看一下Formatter類的相關源碼,可以發現使用的舍入模式是HALF_UP(代碼第11行):

阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題

若想使用其他舍入方式,可設定DecimalFormat

阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題

當把這倆浮點數向下舍入取2位小數時,輸出分别是3.35、3.34,還是因為浮點數無法精确存儲。

是以即使通過DecimalFormat精确控制舍入方式,double/float也可能産生奇怪結果,是以

4.2 字元串格式化也要使用BigDecimal

  • BigDecimal分别使用向下舍入、四舍五入取1位小數格式化數字3.35
  • 阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題
  • 3.3和3.4,符合預期。

最佳實踐:應該使用BigDecimal來進行浮點數的表示、計算、格式化。

5 equals做判等就一定對?

包裝類的比較要通過equals,而非

==

。那使用

equals

對兩個BigDecimal判等,一定符合預期嗎?

  • 使用

    equals

    比較1.0和1這倆BigDecimal:
  • 阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題
  • 結果自然是false。BigDecimal的equals比較的是BigDecimal的value和scale:1.0的scale是1,1的scale是0,是以結果false
  • 阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題
  • 若隻想比較BigDecimal的value,使用compareto
  • 阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題
  • BigDecimal的

    equals

    hashCode

    會同時考慮value和scale,若結合HashSet/HashMap可能出問題。把值為1.0的BigDecimal加入HashSet,然後判斷其是否存在值為1的BigDecimal,得到false
  • 阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題

5.1 解決方案

5.1.1 使用TreeSet替換HashSet

TreeSet不使用hashCode,也不使用equals比較元素,而使用compareTo方法。

阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題

5.1.2 去掉尾部的零

把BigDecimal存入HashSet或HashMap前,先使用stripTrailingZeros方法去掉尾部的零。

比較的時候也去掉尾部的0,確定value相同的BigDecimal,scale也是一緻的:

阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題

6 溢出問題

所有的基本數值類型都有超出儲存範圍可能性。

  • 對Long最大值+1
  • 阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題
  • 結果是一個負數,Long的最大值+1變為了Long的最小值

    -9223372036854775808

顯然發生溢出還沒抛任何異常。

6.1 解決方案

6.1.1 使用Math類的xxExact進行數值運算

這些方法會在數值溢出時主動抛異常。

阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題

執行後,會得到ArithmeticException,這是一個RuntimeException:

java.lang.ArithmeticException: long overflow      

6.1.2 使用大數類BigInteger

BigDecimal專于處理浮點數的專家,而BigInteger則專于大數的科學計算。

使用BigInteger對Long最大值進行+1操作。若想把計算結果轉為Long變量,可使用BigInteger#longValueExact,在轉換出現溢出時,同樣會抛出ArithmeticException

阿裡P8級架構師怎麼處理電商業務中的數值計算的精度/舍入/溢出問題?(下)4 浮點數的舍入和格式化5 equals做判等就一定對?6 溢出問題
9223372036854775808
java.lang.ArithmeticException: BigInteger out of long range      

通過BigInteger對Long的最大值加1無問題,但将結果轉為Long時,則會提示溢出。

參考