做电商的系统都会有交易环节,涉及到交易就会有对账需求。有消费者的对账,也有公司财务的对账。在做对账功能时,很多小伙伴就经常碰到以下几个场景。
场景一:
财务同事:为何这个月我们算的金额和你们系统算的金额差了几分钱啊?
开发同事:差几分钱而已嘛?系统算的流水账对就行啦,你们财务这个月忽略好不?
悲剧的是差一分钱对财务来说是很严重的问题了。
场景二:
用户:我账户充值xxxx元了,扣除手续费,为何实际到账少了一分钱?
开发同事:…嗯?这是银行的四舍五入问题,不是我们的问题,我们帮你加上一分钱吧。。
其实银行算的手续费都是有固定的四舍五入规则的,如何知道这个算法规则,是不会和银行算的有误差的。
到此,有些小伙伴就会提出建议了,“开发时用bigdecimal代替double类型,因为double类型会有精度丢失情况”。这确实是正确的方法,但前段时间有同学找到我说他已经用了bigdecimal类型了为何跟财务对账时还是有误差?今天我们就来讨论一下如何避免这个问题。
导致误差的两个原因代码层面
开发时实体类字段使用了double或float类型,float和double类型主要是为了科学计算和工程计算而设计的。他们执行二进制浮点运算,这是为了在广泛的数字范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果,所以我们不应该用于精确计算的场合,在java中需要用bigdecimal作为金额字段。
同理,数据库表若用了double或float类型在sum的时候往往就出现小数点后面多个小数了,因此数据表金额字段应用decimal(0,2)即保存小数点后面两位小数(具体多少位小数看业务而定)。
业务层面
当代码用了bigdecimal,数据也用了decimal了,为何还会出现前言中的误差呢?原因就是四舍五入的规则不同。
举个我们实际生产中的例子,我曾做过线下pos收款业务系统,其中pos对接的银企直联有交通银行,民生银行等等。由于信用卡消费是T+1到账的,所以在第二天我们才知道银行实际的到账金额与产生的手续费。但客户是不能等一天的,因此手续费由我们系统算出并往客户的余额上加上扣除手续费后的金额。
有天我们发现客户账上充值的金额比银行到账时的金额多了一分钱,我们研究后的结论如下,我们在做四舍五入时用于四舍五入的小数位数与银行的不同。如18.8444与18.84448得到的结果分别是18.84与18.85,银行普遍的算法是从小数点后面第六位开始参与四舍五入,我们取了小数点的第四位了。