当前位置:网站首页>金额计算用 BigDecimal 就万无一失了?看看这五个坑吧~~
金额计算用 BigDecimal 就万无一失了?看看这五个坑吧~~
2022-07-04 15:17:00 【Young丶】
看到一篇因为在金额计算中没有使用BigDecimal
而导致故障的文章,但是除非在一些非常简单的场景,结算汇金类的业务也不会直接用BigDecimal
来计算金额,原因有两点:
BigDecimal
里面还是有很多隐蔽的坑的BigDecimal
没有提供金额的单位
1. BigDecimal
中的五个容易踩的坑
1.1 new BigDecimal()
还是BigDecimal#valueOf()
?
先看下面这段代码
BigDecimal bd1 = new BigDecimal(0.01);
BigDecimal bd2 = BigDecimal.valueOf(0.01);
System.out.println("bd1 = " + bd1);
System.out.println("bd2 = " + bd2);
输出到控制台的结果是:
bd1 = 0.01000000000000000020816681711721685132943093776702880859375
bd2 = 0.01
造成这种差异的原因是0.1这个数字计算机是无法精确表示的,送给BigDecimal
的时候就已经丢精度了,而BigDecimal#valueOf
的实现却完全不同
public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}
它使用了浮点数相应的字符串来构造BigDecimal
对象,因此避免了精度问题。所以大家要尽量要使用字符串而不是浮点数去构造BigDecimal
对象,如果实在不行,就使用BigDecimal#valueOf()
方法吧。
1.2 等值比较
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
System.out.println(bd1.equals(bd2));
System.out.println(bd1.compareTo(bd2));
控制台的输出将会是:
false
0
究其原因是,BigDecimal
中equals
方法的实现会比较两个数字的精度,而compareTo
方法则只会比较数值的大小。
1.3 BigDecimal
并不代表无限精度
先看这段代码
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
a.divide(b) // results in the following exception.
结果会抛出异常:
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
关于这个异常,Oracle的官方文档有具体说明
If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.
大意是,如果除法的商的结果是一个无限小数但是我们期望返回精确的结果,那程序就会抛出异常。回到我们的这个例子,我们需要告诉JVM
我们不需要返回精确的结果就好了
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
a.divide(b, 2, RoundingMode.HALF_UP)// 0.33
1.4 BigDecimal
转回String
要小心
BigDecimal d = BigDecimal.valueOf(12334535345456700.12345634534534578901);
String out = d.toString(); // Or perform any formatting that needs to be done
System.out.println(out); // 1.23345353454567E+16
可以看到结果已经被转换成了科学计数法,可能这个并不是预期的结果BigDecimal
有三个方法可以转为相应的字符串类型,切记不要用错:
String toString(); // 有必要时使用科学计数法
String toPlainString(); // 不使用科学计数法
String toEngineeringString(); // 工程计算中经常使用的记录数字的方法,与科学计数法类似,但要求10的幂必须是3的倍数
1.5 执行顺序不能调换(乘法交换律失效)
乘法满足交换律是一个常识,但是在计算机的世界里,会出现不满足乘法交换律的情况
BigDecimal a = BigDecimal.valueOf(1.0);
BigDecimal b = BigDecimal.valueOf(3.0);
BigDecimal c = BigDecimal.valueOf(3.0);
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP).multiply(c)); // 0.990
System.out.println(a.multiply(c).divide(b, 2, RoundingMode.HALF_UP)); // 1.00
别小看这这0.01的差别,在汇金领域,会产生非常大的金额差异。
2. 最佳实践
关于金额计算,很多业务团队会基于BigDecimal
再封装一个Money
类,其实我们直接可以用一个半官方的Money
类:JSR 354 ,虽然没能在Java 9
中成为Java
标准,很有可能集成到后续的Java
版本中成为官方库。
2.1 maven
坐标
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.1</version>
</dependency>
2.2 新建Money
类
CurrencyUnit cny = Monetary.getCurrency("CNY");
Money money = Money.of(1.0, cny);
// 或者 Money money = Money.of(1.0, "CNY");
//System.out.println(money);
2.3 金额运算
CurrencyUnit cny = Monetary.getCurrency("CNY");
Money oneYuan = Money.of(1.0, cny);
Money threeYuan = oneYuan.add(Money.of(2.0, "CNY")); //CNY 3
Money tenYuan = oneYuan.multiply(10); // CNY 10
Money fiveFen = oneYuan.divide(2); //CNY 0.5
2.4 比较相等
Money fiveFen = Money.of(0.5, "CNY"); //CNY 0.5
Money anotherFiveFen = Money.of(0.50, "CNY"); // CNY 0.50
System.out.println(fiveFen.equals(anotherFiveFen)); // true
可以看到,这个类对金额做了显性的抽象,增加了金额的单位,也避免了直接使用BigDecimal
的一些坑。
边栏推荐
- 新的职业已经出现,怎么能够停滞不前 ,人社部公布建筑新职业
- Go language loop statement (under Lesson 10)
- Years of training, towards Kata 3.0! Enter the safe container experience out of the box | dragon lizard Technology
- APOC自定义函数和过程
- 多年锤炼,迈向Kata 3.0 !走进开箱即用的安全容器体验之旅| 龙蜥技术
- c# 实现定义一套中间SQL可以跨库执行的SQL语句
- Smart Logistics Park supply chain management system solution: digital intelligent supply chain enables a new supply chain model for the logistics transportation industry
- Go development: how to use go singleton mode to ensure the security of high concurrency of streaming media?
- Visual Studio 2019 (LocalDB)MSSQLLocalDB SQL Server 2014 数据库版本为852无法打开,此服务器支持782
- 表单传递时,如何隐式将值传过去
猜你喜欢
Readis configuration and optimization of NoSQL (final chapter)
Software Engineer vs Hardware Engineer
世界环境日 | 周大福用心服务推动减碳环保
如何实现一个延时队列 ?
C# 更加优质的操作MongoDB数据库
Understand asp Net core - Authentication Based on jwtbearer
Object.keys()的用法
多年锤炼,迈向Kata 3.0 !走进开箱即用的安全容器体验之旅| 龙蜥技术
How to decrypt worksheet protection password in Excel file
Principle and general steps of SQL injection
随机推荐
Software Engineer vs Hardware Engineer
Statistical learning: logistic regression and cross entropy loss (pytoch Implementation)
C# 服务器日志模块
矿产行业商业供应链协同系统解决方案:构建数智化供应链平台,保障矿产资源安全供应
How to implement a delay queue?
Array filter fliter in JS
Years of training, towards Kata 3.0! Enter the safe container experience out of the box | dragon lizard Technology
How to contribute to the source code of ongdb core project
Research Report of exoskeleton robot industry - market status analysis and development prospect prediction
S2b2b solution for lighting industry: efficiently enable the industrial supply chain and improve the economic benefits of enterprises
Research Report on plastic recycling machine industry - market status analysis and development prospect forecast
对人胜率84%,DeepMind AI首次在西洋陆军棋中达到人类专家水平
《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(2)-初识Fiddler让你理性认识一下
C # realizes FFT forward and inverse transformation and frequency domain filtering
智慧物流園區供應鏈管理系統解决方案:數智化供應鏈賦能物流運輸行業供應鏈新模式
嵌入式软件架构设计-函数调用
Visual studio 2019 (localdb) mssqllocaldb SQL Server 2014 database version is 852 and cannot be opened. This server supports 782
Accounting regulations and professional ethics [11]
Redis: SDS source code analysis
多年锤炼,迈向Kata 3.0 !走进开箱即用的安全容器体验之旅| 龙蜥技术