【踩坑系列】使用long类型处理金额,科学计数法导致金额转大写异常
2020-10-26 09:53 申城异乡人 阅读(2097) 评论(2) 编辑 收藏 举报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种解决方案:
- 使用NumberFormat
- 使用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使用指南。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· DeepSeek本地性能调优
· 一文掌握DeepSeek本地部署+Page Assist浏览器插件+C#接口调用+局域网访问!全攻略