Hi, I’m Shendi
记一次金额操作精度丢失问题与解决
前言
在之前做过几个涉及到金额的项目,因我最开始接触的支付是微信支付,对于微信的设计,是以分为单位,金额为整数形式。知晓精度丢失的问题,我也照着这样,将金额设计成整数。
对于小数存储,在代码中使用的double,因为数据库中是以整数存储,而小数计算的地方实际上不是很多,只有在给用户展示时将 金额/100
,以及在一些输入或接收到的金额为小数字符串(支付宝支付)的时候,将 金额*100
。
想得挺简单,因为在我的认知中精度只有两位小数,而且进行的操作都是小数乘以或除以100,出现精度丢失的可能性应该不大
在之前编写了一个另类支付方式,一个 SPay APP,涉及到了金额的截取,发现过精度丢失的问题,是因为默认小数是double类型,我在计算后使用float类型存储,导致精度丢失,于是使用double存储问题就解决了。也尝试将数字进行替换进行测试,没有发现任何问题…
问题出现
在今天,发现了精度丢失的问题。我的代码是这样的
int amount = (int) (Double.parseDouble(amountStr) * 100);
其中 amountStr 为 0.58,最后的结果为 57。
测试与解决
在之前我觉得像这种 * 100与 / 100的操作按道理来说应该不会出现精度丢失问题,但现在问题就这样摆在眼前。
我将Double换成float,问题就解决了。
但就像之前一样,我不确定这样在我的场景中是否还会出现精度丢失问题。于是开始寻找对于我这种只有两位小数的关于 float 精度丢失的示例,但并没有结果。
我知道关于金额操作,保持高精度有一种解决办法是使用 BigDecimal
,但这对性能有一定的损耗。就像上面这样的示例,简单试一下运行时间
大概是测试不够严谨,使用BigDecimal与直接基础类型计算耗时居然相似!
既然这样,那就直接选择使用BigDecimal了。
经过测试,需要注意的是,创建BigDecimal最好是使用字符串。否则也有可能出现精度丢失问题,例如上面说的0.58*100,如果不是字符串,依然精度丢失…
总结:
对于重要的小数操作皆使用BigDecimal,且能用字符串创建尽量用字符串创建。这样,大概就不会再出现精度丢失这种令人头疼又不可控的问题了吧?
吐槽
一个好的东西一定是站在使用者的角度上来考虑,对于上面这样精度丢失的问题属实唏嘘,为什么不直接提供一个不会丢失精度的类型呢?毕竟我们只是使用者,关心的大多是业务与流程,对于性能部分那都是后话了。
自然而然的就有着这样的疑问,为什么不用BigDecimal将double与float取代?默认小数类型使用BigDecimal,毕竟,谁也不想自己写的程序好好的突然出现bug然后去更改吧。
但存在即合理,那些开发者们大抵考虑过这种问题,可能权衡利弊后,选择了保持现状。
END