java项目中与金额有关的计算注意事项
前言:
在项目中,财务相关的数据计算可能会通过项目进行各种计算以及存库,本篇讲述这个过程中的注意事项。
1.浮点型的误差
浮点型由于其定义的原因,在使用的时候总会存在误差,以下以pgsql为例:
sql
select 0.1::float+0.2::float; -- 返回结果:0.30000000000000004
可以看到这就出现了误差,尾数末尾有个4,在计算中进行比较的时候可能就会出现错误,比如0.1+0.2 > 0.3了。
所以在项目中与金额有关的计算全部避免使用浮点型,像java中的double,float以及对应的封装类,pgsql中的float等,均避免使用。
2.数据库中如何存储
第一小节讲述了浮点型可能造成的误差,那么数据库中应该如何存储呢,以pgsql为例,应当使用decimal或者numeric存储,这种类型的固定精度和标度的数值可以避免浮点数误差导致的问题。
3.java中如何避免浮点精度问题
java中有一个BigDecimal类,与pgsql中decimal和numeric类似,都是固定精度和标度的数值,都不会有浮点型的误差,但是需要注意的是,在使用的时候不能使用Double接收了前端参数之后,再由这个double类型初始化BigDecimal,从前端接受和后端取库中数据以及计算,应当全程使用BigDecimal,避免中间任意一环使用浮点数。
4.其他案例
在避免了浮点数精度导致的问题的时候,金额计算可能会伴随着其他问题,下面进行举例:
以一个简单的奖金计算为例,一个项目有若干个里程碑,每个里程碑有对应的进度,里程碑达成后,会生成里程碑进度×项目金额的奖金,由于金额的最小单位是分,所以数据库中存储保留两位小数并且去除多余尾数的时候向下取整。假设项目金额10000.57,有两个里程碑,第一个里程碑进度33%,第二个里程碑进度67%,总和为100%。
当第一个里程碑达成的时候,获得的奖金为10000.57×0.33=3,300.1881,保留两位小数后获得结果3,300.18,第二个里程碑达成后获取奖金10000.57×0.67=6,700.3819,保留两位小数取得结果6700.38,两次奖金相加3300.18+6700.38=10000.56,总的奖金生成少了1分钱,这一分钱看似无所谓,财务计算的时候能要命。
- 那么应该如何避免呢?
- 首先这个问题是怎么造成的呢?这个问题是因为进行了保留小数的操作,造成的,那么就有两种方案:
- 不舍去多余小数。(但是如果一定要舍去的情况咋办呢qwq,其实讲这个案例就是为了说明保留小数位数后造成最后总金额算出来不对)
- 在某个环节补回来,上面这个案例就可以在最后一个里程碑达成之后,和总奖金进行计算,将误差的金额补回来。
- 首先这个问题是怎么造成的呢?这个问题是因为进行了保留小数的操作,造成的,那么就有两种方案:
5.总结
- 数据库存储金额的时候避免使用浮点数,以pgsql为例,使用numeric或者decimal。
- java中避免浮点数精度问题的时候,从接受前端数据到后端存库以及中间计算,全程使用BigDecimal接受前端参数和进行运算以及存库。
- 金额相关的计算都应该慎重考虑,不止浮点数造成的误差,保留小数位数的操作可能也会造成误差,需要在某些环节补回来。
最后欢迎各位大佬批评指正QWQ