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使用指南


【踩坑系列】使用long类型处理金额,科学计数法导致金额转大写异常的更多相关文章

  1. Sql server 的float和real类型会产生科学计数法,如何消除科学计数法

    sqlserver 查询的 float 类型 如果是0.00000000001的话,会被显示为1E-11,请问怎么才能让查询出的结果显示为正常显示方式而不是科学计数法? 答案: float 和 rea ...

  2. [小问题笔记(十)] SQL Server 里 float 转 varchar等字符类型 不使用科学计数法

    需要转换两次, 试了一下 float 转 bigint 转 varchar 溢出了... 后来用  float 转 decimal(38,0) 转 varchar 就成功了~ ,)) )) 另吐槽一下 ...

  3. 如何使java中double类型不以科学计数法表示

    在java中,把一个double或者BigDecimal的小数转换为字符串时,经常会用科学计数法表示,而我们一般不想使用科学计数法,可以通过:DecimalFormat a = new Decimal ...

  4. python踩坑系列之导入包时下划红线及报错“No module named”问题

    python踩坑系列之导入包时下划红线及报错“No module named”问题 使用pycharm编写Python时,自己写了一个包(commontool),在同级另一个路径下(fileshand ...

  5. jmeter踩坑系列

    1.踩坑系列一: 抓包出来有host的字段,放到jmeter里面一起请求就报错了,去掉就请求正常了 1.踩坑系列二: 从花瓶复制过去 的values 前面有空格,肉眼看起来没有

  6. WebGL 踩坑系列-3

    WebGL 踩坑系列-3 绘制球体 在 WebGL 中绘制物体时需要的顶点是以直角坐标表示的, 当然了,gl_Position 是一个四维的向量,一般将顶点赋值给 gl_Position 时,最后一维 ...

  7. --mysql 导出数据时, 数字类型的列如果位数过长,变为科学计数法问题

    --mysql 导出数据时, 数字类型的列如果位数过长,变为科学计数法问题在字段前加上\t即可select concat('\t',a.IDCARD_NO) from xxx a

  8. SQL Server 疑难杂症--转换科学计数法的数值字符串为decimal类型

    今天在操作数据库时,需要将字符串转换成Decimal类型.代码如下: select cast('0.12' as decimal(18,2)); select convert(decimal(18,2 ...

  9. electron踩坑系列之一

    前言 以electron作为基础框架,已经开发两个项目了.第一个项目,我主要负责用react写页面,第二项目既负责electron部分+UI部分. 做项目,就是踩坑, 一路做项目,一路踩坑,坑多不可怕 ...

随机推荐

  1. 解释器与JIT编译器

    解释器 JVM设计者们的初衷仅仅只是单纯地为了满足Java程序实现跨平台特性,因此避免采用静态编译的方式直接生成本地机器指令,从而诞生了实现解释器在运行时采用逐行解释字节码执行程序的想法. 解释器真正 ...

  2. Redis学习(二)redis的特点

    一.Redis的特性 Redis是基于内存,常用作于缓存的技术 Redis实现的是分布式缓存,如果有多台实例(机器)的话,每个实例都共享一份缓存,缓存具有一致性. 常见的性能问题一般都是由于数据库(磁 ...

  3. 吴恩达-机器学习+正则化regularization

  4. 手撸Mysql原生语句--增删改查

    mysql数据库的增删改查有以下的几种的情况, 1.DDL语句 数据库定义语言: 数据库.表.视图.索引.存储过程,例如CREATE DROP ALTER SHOW 2.DML语句 数据库操纵语言: ...

  5. java实现点击查询数据生成excel文件并下载

    须先导入关键maven包 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi& ...

  6. 项目升级springboot2.0注意事项

    一.pring boot 2.0以后, springboot jpa findById 返回类型变化@NoRepositoryBeanpublic interface CrudRepository&l ...

  7. CentOS7 【linux系统】配置 JDK 教程

    1. 下载 [linux版本] JDK 1.8 的包. 2. 导入linux系统里面. 如何导入,下载一个winSCP 软件 破解安装,然后再linux 系统里面 查询IP,连接即可. 在linux解 ...

  8. MySQL 8.0索引合并

    简介 参考https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html#index-merge-intersection. ...

  9. 引用类型之Object

    引用类型 引用类的值(对象)是引用类型的一个实例.在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起. 对象是某个特定引用类型的实例.新对象是使用new操作符后跟一个构造函数 ...

  10. 【字符串算法】字典树(Trie树)

    什么是字典树 基本概念 字典树,又称为单词查找树或Tire树,是一种树形结构,它是一种哈希树的变种,用于存储字符串及其相关信息. 基本性质 1.根节点不包含字符,除根节点外的每一个子节点都包含一个字符 ...