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

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

1. 踩坑經歷

上週,一個使用者反饋他建立的某個銷售單無法開啟,但其餘銷售單都可以正常開啟,當時查看了生產環境的ERROR日誌,發現拋了這樣的異常:java.lang.NumberFormatException: For input string: "E"

相信大家對這個異常都不陌生,很顯然,是因為將字串轉換為數字時丟擲的,比如下面這樣:

但仔細查看了使用者報錯的單據,也沒有發現哪裡有輸入“E”這樣的字串(請原諒我第一時間沒有想到是科學計數法造成的,哈哈),最後把生產環境的這條資料插入到了開發環境中,定位到原來是因為將金額轉換為大寫時導致的,報錯的關鍵程式碼如下所示:

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"

最後的解決方案是將金額轉換為BigDecimal來處理,程式碼修改為如下所示:

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位及以上,會以科學計數法表示,如下所示:

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);

默默數了下,整數部分8位的話,都是千萬級別了,估計遇到這個問題的使用者很豪,哈哈。

所以使用double來表示金額,當金額遇到科學計數法時,就會顯示不正常、甚至造成一些意想不到的異常。

3. 解決方案

如果不想用科學計數法顯示,而是顯示金額本身,有以下2種解決方案:

  1. 使用NumberFormat
  2. 使用BigDecimal

3.1 方案一:使用NumberFormat

使用NumberFormat的方法如下所示:

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));

當將numberFormat.setGroupingUsed(false);註釋掉或者修改為numberFormat.setGroupingUsed(true);時,輸出結果就變為了:

3.2 方案二:使用BigDecimal(推薦)

使用BigDecimal的方法如下所示:

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());

相比而言,我更推薦使用BigDecimal的這種方案。

關於BigDecimal的更多用法,可以檢視我寫的另一篇部落格:Java BigDecimal使用指南