当前位置:网站首页>金额计算用 BigDecimal 就万无一失了?看看这五个坑吧~~
金额计算用 BigDecimal 就万无一失了?看看这五个坑吧~~
2022-07-04 13:18:00 【Hollis Chuang】
Hollis的新书限时折扣中,一本深入讲解Java基础的干货笔记!
看到一篇因为在金额计算中没有使用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.331.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.52.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的一些坑。
完
我的新书《深入理解Java核心技术》已经上市了,上市后一直蝉联京东畅销榜中,目前正在6折优惠中,想要入手的朋友千万不要错过哦~长按二维码即可购买~

长按扫码享受6折优惠
往期推荐
为什么都想去国企?技术落后,薪资低,进去以后躺平几年,出来都找不到工作!
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号

好文章,我在看️
边栏推荐
- Real time data warehouse
- Chapter 16 string localization and message Dictionary (2)
- 一种架构来完成所有任务—Transformer架构正在以一己之力统一AI江湖
- [MySQL from introduction to proficiency] [advanced chapter] (V) SQL statement execution process of MySQL
- [information retrieval] link analysis
- opencv3.2 和opencv2.4安装
- Stm32f1 and stm32subeide programming example -max7219 drives 8-bit 7-segment nixie tube (based on GPIO)
- A collection of classic papers on convolutional neural networks (deep learning classification)
- 【算法leetcode】面试题 04.03. 特定深度节点链表(多语言实现)
- 流行框架:Glide的使用
猜你喜欢

Nowcoder rearrange linked list
![[MySQL from introduction to proficiency] [advanced chapter] (IV) MySQL permission management and control](/img/cc/70007321395afe3a9fc6b6032d30aa.png)
[MySQL from introduction to proficiency] [advanced chapter] (IV) MySQL permission management and control

Pandora IOT development board learning (RT thread) - Experiment 3 button experiment (learning notes)

实战解惑 | OpenCV中如何提取不规则ROI区域

【MySQL从入门到精通】【高级篇】(四)MySQL权限管理与控制

Digi XBee 3 RF: 4个协议,3种封装,10个大功能

LVGL 8.2 Draw label with gradient color

Opencv learning notes - linear filtering: box filtering, mean filtering, Gaussian filtering
![[information retrieval] experiment of classification and clustering](/img/05/ee3b3bc4ab79d52b63cdc34305aa57.png)
[information retrieval] experiment of classification and clustering

LVGL 8.2 LED
随机推荐
flink sql-client. SH tutorial
阿里被裁员工,找工作第N天,猎头又传来噩耗...
失败率高达80%,企业数字化转型路上有哪些挑战?
SqlServer函数,存储过程的创建和使用
A collection of classic papers on convolutional neural networks (deep learning classification)
leetcode:6109. 知道秘密的人数【dp的定义】
【MySQL从入门到精通】【高级篇】(五)MySQL的SQL语句执行流程
LVGL 8.2 Line wrap, recoloring and scrolling
迅为IMX6Q开发板QT系统移植tinyplay
Digi XBee 3 RF: 4个协议,3种封装,10个大功能
leetcode:6110. The number of incremental paths in the grid graph [DFS + cache]
开发中常见问题总结
为什么国产手机用户换下一部手机时,都选择了iPhone?
RK1126平台OSD的实现支持颜色半透明度多通道支持中文
一种架构来完成所有任务—Transformer架构正在以一己之力统一AI江湖
Leetcode t47: full arrangement II
Popular framework: the use of glide
电商系统中红包活动设计
Map of mL: Based on Boston house price regression prediction data set, an interpretable case is realized by using the map value to the LIR linear regression model
R language uses bwplot function in lattice package to visualize box plot and par Settings parameter custom theme mode


