4 浮點數的舍入和格式化
應考慮顯式編碼,通過格式化表達式或格式化工具
4.1 明确小數位數和舍入方式
- 通過String.format使用
格式化double/float的3.35浮點數%.1f
-
結果
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行):
若想使用其他舍入方式,可設定DecimalFormat
當把這倆浮點數向下舍入取2位小數時,輸出分别是3.35、3.34,還是因為浮點數無法精确存儲。
是以即使通過DecimalFormat精确控制舍入方式,double/float也可能産生奇怪結果,是以
4.2 字元串格式化也要使用BigDecimal
- BigDecimal分别使用向下舍入、四舍五入取1位小數格式化數字3.35
- 3.3和3.4,符合預期。
最佳實踐:應該使用BigDecimal來進行浮點數的表示、計算、格式化。
5 equals做判等就一定對?
包裝類的比較要通過equals,而非
==
。那使用
equals
對兩個BigDecimal判等,一定符合預期嗎?
- 使用
比較1.0和1這倆BigDecimal:equals
- 結果自然是false。BigDecimal的equals比較的是BigDecimal的value和scale:1.0的scale是1,1的scale是0,是以結果false
- 若隻想比較BigDecimal的value,使用compareto
- BigDecimal的
和equals
會同時考慮value和scale,若結合HashSet/HashMap可能出問題。把值為1.0的BigDecimal加入HashSet,然後判斷其是否存在值為1的BigDecimal,得到falsehashCode
5.1 解決方案
5.1.1 使用TreeSet替換HashSet
TreeSet不使用hashCode,也不使用equals比較元素,而使用compareTo方法。
5.1.2 去掉尾部的零
把BigDecimal存入HashSet或HashMap前,先使用stripTrailingZeros方法去掉尾部的零。
比較的時候也去掉尾部的0,確定value相同的BigDecimal,scale也是一緻的:
6 溢出問題
所有的基本數值類型都有超出儲存範圍可能性。
- 對Long最大值+1
-
結果是一個負數,Long的最大值+1變為了Long的最小值
-9223372036854775808
顯然發生溢出還沒抛任何異常。
6.1 解決方案
6.1.1 使用Math類的xxExact進行數值運算
這些方法會在數值溢出時主動抛異常。
執行後,會得到ArithmeticException,這是一個RuntimeException:
java.lang.ArithmeticException: long overflow
6.1.2 使用大數類BigInteger
BigDecimal專于處理浮點數的專家,而BigInteger則專于大數的科學計算。
使用BigInteger對Long最大值進行+1操作。若想把計算結果轉為Long變量,可使用BigInteger#longValueExact,在轉換出現溢出時,同樣會抛出ArithmeticException
9223372036854775808
java.lang.ArithmeticException: BigInteger out of long range
通過BigInteger對Long的最大值加1無問題,但将結果轉為Long時,則會提示溢出。
參考