【踩坑系列】使用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使用指南



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM