
有时候我们写的代码需要跟钱打交道,比如张三充值多少,需要减掉多少钱,增加多少钱之类的。
很早之前就似乎就听到这样的说法:钱的问题,如果不嫌麻烦,全部转化为分,或者使用BigDecimal就好了。
转化为分的方法是说,如果用户输入1块钱,系统存的是100分,它存的是整数,对于加减来说不存在丢失精度的问题。对于乘除可能会丢失精度,比如打折之类的,但这种丢失并不常见。打折丢失精度也可以接受,毕竟打折本身也算是把钱让渡出去。
还有一种就是使用BigDecimal,在代码和数据库中都使用这种数据类型。用户输入多少,就存入多少。
一切相安无事,我快乐安心的使用着BigDecimal,直到有一天,同事说我的这段代码可能有问题,我立马意识到问题了:
BigDecimal sum = BigDecimal.ZERO;
//计算货物清单中所有金额,并返回。返回的数值精度为2,四舍五入
for(BuyDetail detail : list){
BigDecimal amount = detail.getAmount();
sum = sum.add(amount).scale(2,BigDecimal.ROUND_HALF_UP);
}
return sum;
在每次累加计算的时候,就进行四舍五入的操作,这样计算结果就会有累计误差。只要次数足够多,就会出问题。应该要放入到返回的时候,才进行四舍五入。于是我改成了这样:
BigDecimal sum = BigDecimal.ZERO;
for(BuyDetail detail : list){
BigDecimal amount = detail.getAmount();
sum = sum.add(amount);
}
return sum.scale(2,BigDecimal.ROUND_HALF_UP);
看似不起眼的问题,也可能造成很大的问题,并且很难说清楚哪一步有问题。
这不禁让我思考,为啥我会设置精度,进行四舍五入的操作呢,之前我可是使用BigDecimal很快乐的进行加减乘除的操作,并不会设置什么精度。
于是想到了之前使用BigDecimal进行除法操作,没有设置精度,就会造成报错,因为两数相除,得到了一个无线不循环的数值,计算机是没办法表示这个值的,你必须手动指定精度和四舍五入的方式。于是后面不管三七二十一,每一步操作我都会去设置精度,至少,对于眼前,是不会报错的。
实际上说到底,是因为我的知道的太少,而故意掩盖了问题。对于上面,每次执行加法操作,不存在会出现无限小数的问题,所以,不设置精度才是最好的答案。
我在网上寻找更多关于BigDecimal的操作,发现了一个让人不安的现象:
System.out.println(
new BigDecimal(0.1)
.add(new BigDecimal(0.006))
);
System.out.println(
new BigDecimal("0.1")
.add(new BigDecimal("0.006"))
);
它们执行之后,打印如下:
0.10600000000000000567601521339611281291581690311431884765625
0.106
很明显,第一个的计算出现了问题。第二个使用字符串的方式,计算没有问题。
那我平时将前端的数值直接映射为后端的BigDecimal不是也有问题吗?正确的做法是将前端的数值当做字符串来对待,后端代码中手动处理转换过程,再进行数值的计算。
网友评论