1. 程式人生 > >【踩坑系列】使用long型別處理金額,科學計數法導致金額轉大寫異常

【踩坑系列】使用long型別處理金額,科學計數法導致金額轉大寫異常

## 1. 踩坑經歷 上週,一個使用者反饋他建立的某個銷售單無法開啟,但其餘銷售單都可以正常開啟,當時查看了生產環境的ERROR日誌,發現拋了這樣的異常:`java.lang.NumberFormatException: For input string: "E"`。 相信大家對這個異常都不陌生,很顯然,是因為將字串轉換為數字時丟擲的,比如下面這樣: ![](https://images.zwwhnly.com/picture/2020/10/snipaste_20201023_143312.png) 但仔細查看了使用者報錯的單據,也沒有發現哪裡有輸入“E”這樣的字串(請原諒我第一時間沒有想到是科學計數法造成的,哈哈),最後把生產環境的這條資料插入到了開發環境中,定位到原來是因為將金額轉換為大寫時導致的,報錯的關鍵程式碼如下所示: ```java String totalAmountStr = String.valueOf(totalAmount / 100.0); String amountCN = MoneyUtils.toChinese(totalAmountStr); ``` 其中totalAmount是一個long型別的變數,之所以除以100.0,是因為我們資料庫中儲存金額都是按**分**為單位儲存的(相信很多小夥伴也是這麼儲存的),第2行程式碼主要是為了將金額轉換為大寫,比如將105000.50轉換為壹拾萬零伍仟元伍角。 使用者報錯的那個單據,totalAmount為2700萬,轉換為分就是:2700000000,執行完`totalAmount / 100.0`,輸出結果竟然是2.7E7,而不是預期的27000000,因此導致了異常:`java.lang.NumberFormatException: For input string: "E"`。 ![](https://images.zwwhnly.com/picture/2020/10/snipaste_20201023_144239.png) 最後的解決方案是將金額轉換為BigDecimal來處理,程式碼修改為如下所示: ```java String totalAmountStr = new BigDecimal(String.valueOf(totalAmount)).divide(new BigDecimal("100"),2, RoundingMode.HALF_UP).toString(); String amountCN = MoneyUtils.toChinese(totalAmountStr); ``` ## 2. 原因分析 在Java中,當浮點數(float、double)的整數部分達到8位及以上,會以科學計數法表示,如下所示: ```java double firstAmount = 2700000D; double secondAmount = 27000000D; double thirdAmount = 2700000.25D; double fourthAmount = 27000000.25D; System.out.println(firstAmount); System.out.println(secondAmount); System.out.println(thirdAmount); System.out.println(fourthAmount); ``` ![](https://images.zwwhnly.com/picture/2020/10/snipaste_20201023_150340.png) > 默默數了下,整數部分8位的話,都是千萬級別了,估計遇到這個問題的使用者很豪,哈哈。 所以使用double來表示金額,當金額遇到科學計數法時,就會顯示不正常、甚至造成一些意想不到的異常。 ## 3. 解決方案 如果不想用科學計數法顯示,而是顯示金額本身,有以下2種解決方案: 1. 使用NumberFormat 2. 使用BigDecimal ### 3.1 方案一:使用NumberFormat 使用NumberFormat的方法如下所示: ```java NumberFormat numberFormat = NumberFormat.getInstance(); numberFormat.setGroupingUsed(false); double secondAmount = 27000000D; double fourthAmount = 27000000.25D; System.out.println(numberFormat.format(secondAmount)); System.out.println(numberFormat.format(fourthAmount)); ``` ![](https://images.zwwhnly.com/picture/2020/10/snipaste_20201023_152314.png) 當將`numberFormat.setGroupingUsed(false);`註釋掉或者修改為`numberFormat.setGroupingUsed(true);`時,輸出結果就變為了: ![](https://images.zwwhnly.com/picture/2020/10/snipaste_20201023_152629.png) ### 3.2 方案二:使用BigDecimal(推薦) 使用BigDecimal的方法如下所示: ```java double secondAmount = 27000000D; double fourthAmount = 27000000.25D; System.out.println(new BigDecimal(String.valueOf(secondAmount)).setScale(2,RoundingMode.HALF_UP).toString()); System.out.println(new BigDecimal(String.valueOf(fourthAmount)).setScale(2,RoundingMode.HALF_UP).toString()); ``` ![](https://images.zwwhnly.com/picture/2020/10/snipaste_20201023_153142.png) 相比而言,我更推薦使用BigDecimal的這種方案。 關於BigDecimal的更多用法,可以檢視我寫的另一篇部落格:[Java BigDecimal使用指南](https://www.cnblogs.com/zwwhnly/p/13575828.html)。