【踩坑系列】使用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种解决方案:
- 使用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使用指南。
【踩坑系列】使用long类型处理金额,科学计数法导致金额转大写异常的更多相关文章
- Sql server 的float和real类型会产生科学计数法,如何消除科学计数法
sqlserver 查询的 float 类型 如果是0.00000000001的话,会被显示为1E-11,请问怎么才能让查询出的结果显示为正常显示方式而不是科学计数法? 答案: float 和 rea ...
- [小问题笔记(十)] SQL Server 里 float 转 varchar等字符类型 不使用科学计数法
需要转换两次, 试了一下 float 转 bigint 转 varchar 溢出了... 后来用 float 转 decimal(38,0) 转 varchar 就成功了~ ,)) )) 另吐槽一下 ...
- 如何使java中double类型不以科学计数法表示
在java中,把一个double或者BigDecimal的小数转换为字符串时,经常会用科学计数法表示,而我们一般不想使用科学计数法,可以通过:DecimalFormat a = new Decimal ...
- python踩坑系列之导入包时下划红线及报错“No module named”问题
python踩坑系列之导入包时下划红线及报错“No module named”问题 使用pycharm编写Python时,自己写了一个包(commontool),在同级另一个路径下(fileshand ...
- jmeter踩坑系列
1.踩坑系列一: 抓包出来有host的字段,放到jmeter里面一起请求就报错了,去掉就请求正常了 1.踩坑系列二: 从花瓶复制过去 的values 前面有空格,肉眼看起来没有
- WebGL 踩坑系列-3
WebGL 踩坑系列-3 绘制球体 在 WebGL 中绘制物体时需要的顶点是以直角坐标表示的, 当然了,gl_Position 是一个四维的向量,一般将顶点赋值给 gl_Position 时,最后一维 ...
- --mysql 导出数据时, 数字类型的列如果位数过长,变为科学计数法问题
--mysql 导出数据时, 数字类型的列如果位数过长,变为科学计数法问题在字段前加上\t即可select concat('\t',a.IDCARD_NO) from xxx a
- SQL Server 疑难杂症--转换科学计数法的数值字符串为decimal类型
今天在操作数据库时,需要将字符串转换成Decimal类型.代码如下: select cast('0.12' as decimal(18,2)); select convert(decimal(18,2 ...
- electron踩坑系列之一
前言 以electron作为基础框架,已经开发两个项目了.第一个项目,我主要负责用react写页面,第二项目既负责electron部分+UI部分. 做项目,就是踩坑, 一路做项目,一路踩坑,坑多不可怕 ...
随机推荐
- ams入门了解
另一篇介绍文 https://www.cnblogs.com/clds/p/4985893.html 转载自https://www.cnblogs.com/onlysun/p/4533798.htm ...
- java基础整理总结篇(1)
>>java数据区域,大致分以下几种 寄存器:位于cpu内部,寄存器的数量有限,所以寄存器根据需求分配.不能直接控制它. 堆栈:位于通用RAM(随机访问存储器)中,通过堆栈指针可以从处理器 ...
- 01.vue数据绑定
Vue特点 渐进式: 渐进, 可以理解成一步一步的. 在使用Vue的时候, 我们不需要把整个Vue框架的东西都用上, 可以一步一步的根据需要慢慢的替换之前的代码. 自底向上逐层应用: 由底层开始, 把 ...
- 原子类CAS的底层实现
原子类使用 public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = ...
- Python3.8下载安装步骤及环境变量配置详解
安装地址:https://www.python.org/ 打开python官网网址,点击 Python 3.8.5 3.下载与自己电脑系统相匹配的版本(这里以64为例) 点击下载完成后打开文件运行 点 ...
- golang开发:select多路选择
select 是 Golang 中的一个控制结构,语法上类似于switch 语句,只不过select是用于 goroutine 间通信的 ,每个 case 必须是一个通信操作,要么是发送要么是接收,s ...
- Redis深入浅出
一.基础使用 常用命令 keys,expire(过期),ttl(查看生存时间),set,select,dbsize,flushdb(删除当前库),flushall(删除所有), get,append, ...
- python下正则表达式的随笔记录
使用了下正则的表达式: 目的:取出字符串中{}中的内容 最后使用的正则表达式为 {(.*?)} 先看 .*? : 首先 . 是用来匹配字符串,但是只能匹配一次. 所以加上 * ,可以让 ...
- QT博客:QT学习之路
http://www.qter.org/thread-629-1-1.html
- 深入了解如何构建您的第一个多语言ASP。NET MVC 5 Web应用程序
下载demo - 3.9 MB 介绍 这篇文章解释了如何创建一个简单的多语言ASP.NET MVC 5 Web应用程序.该应用程序将能够处理英语(美国),西班牙语和法语.英语将是默认语言.当然,扩展解 ...