【踩坑系列】使用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部分. 做项目,就是踩坑, 一路做项目,一路踩坑,坑多不可怕 ...
随机推荐
- 20行代码实现,使用Tarjan算法求解强连通分量
今天是算法数据结构专题的第36篇文章,我们一起来继续聊聊强连通分量分解的算法. 在上一篇文章当中我们分享了强连通分量分解的一个经典算法Kosaraju算法,它的核心原理是通过将图翻转,以及两次递归来实 ...
- 面试官:分库分表之后,id 主键如何处理?
面试题 分库分表之后,id 主键如何处理? 面试官心理分析 其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个全 ...
- svg的学习
svg的学习 1,初步了解 1,大致看了一下svg的简介,在图形的操作和展示上有很大的优势,例如不会失精:灵活的dom操作:很好的兼容性(IE需要下载插件).so,是一门值得深究的前端课程: 2,看了 ...
- NX二次开发-使用NXOPEN C#手工搭建开发环境配置
新建类库 进来后编译代码,成功 添加NXOPEN的库到项目中 不同NX版本,可能dll所在位置不一样,NX11以上版本在NXBIN这里,NX11以下版本大概在UGII. 添加头文件 using NXO ...
- SQL注入之Mysql报错注入
--志向和热爱是伟大行为的双翼. 昨天偷懒了没学什么东西,先自我反省一下 - -. 今天认真的学习了一下Mysql报错注入利用方法及原理,好久之前就像认真的学一下这个了,是在上海市大学生网络安全大赛中 ...
- 基础篇:详解锁原理,volatile+cas、synchronized的底层实现
目录 1 锁的分类 2 synchronized底层原理 3 Object的wait和notify方法原理 4 jvm对synchronized的优化 5 CAS的底层原理 6 CAS同步操作的问题 ...
- Python-类属性查询协议-__getattr__ __getattribute__
__getattr__ 查找不到类属性的时候调用 class BeiMenChuiXue: def __init__(self, name): self.name = name def __getat ...
- 解决SpringBoot项目创建缓慢问题
SpringBoot项目构建缓慢 快速创建springboot项目 在创建一个springboot项目的时候,往往速度会很慢,原因是下载springboot文件的默认地址是springboot官网(国 ...
- Lyndon Word相关
Lyndon Word 定义 对于字符串 \(S\),若 \(S\) 的最小后缀为其本身,那么称 \(S\) 为 \(\text{Lyndon}\) 串(\(\text{Lyndon Word}\)) ...
- Java知识系统回顾整理01基础01第一个程序04创建Eclipse项目
一.为Eclipse设置桌面快捷方式图标 二.双击桌面快捷方式打开Eclipse 三.选择工作区 使用在命令行Hello World中的项目目录e:\project 除了第一次启动eclipse的时候 ...