前言:
现在朋友们对“json 精度丢失”大体比较注重,看官们都需要剖析一些“json 精度丢失”的相关内容。那么小编在网上收集了一些对于“json 精度丢失””的相关文章,希望兄弟们能喜欢,朋友们快快来学习一下吧!导读:在实际项目开发中,账务系统、交易系统、支付体系等都会涉及到对于金钱的处理。其中精度丢失问题十分常见,精度丢失对于金钱来说是不可小视的。接触了挺长时间关于互联网金融方面的开发,下面将分享一下关于金钱处理的方式,如何有效地避免开发中精度丢失的问题,希望对大家有所帮助。如果有其他方式或者优化的建议也欢迎共同讨论。
计算机进行浮点型运算为什么会造成精度丢失?
在说解决方式之前,先了解下计算机进行浮点型运算为什么会造成精度丢失。在计算机中数据是按照二进制进行存储的。而在日常应用中我们常使用的是十进制数值,因此数据需要进行一系列进制转换才能正确存储到计算机中,举个例子:
整数11 (整数十进制转二进制采用的方式为除二取余法)11 / 2 = 5 余 15 / 2 = 2 余 1 2 / 2 = 1 余 0 1 / 2 = 0 余 10 结束11 的二进制表示(从下往上):1011注意:只要遇到整除以后的结果为0就结束了,所有的整数整除以2最终一定能够得到0。所以整数转变为二进制数的算法是不会无限循环下去。整数永远可以用二进制精确表示 ,但小数就不一定了。-----------------------------------------------------------------小数0.9 (小数十进制转二进制采用的方式为乘二取整法)0.9*2=1.8 取整数部分 10.8(1.8的小数部分)*2=1.6 取整数部分 10.6*2=1.2 取整数部分 10.2*2=0.4 取整数部分 00.4*2=0.8 取整数部分 00.8*2=1.6 取整数部分 10.6*2=1.2 取整数部分 0......... 0.9二进制表示为(从上往下): 1100100100100......注意:上面的计算过程表示了*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。精度丢失在Java中的表现
对于 Java 中使用float、double来表示浮点数,当使用它们进行计算时往往会出现了精度丢失问题。在《Effective Java》书中给出了建议,float与double只能用来做科学计算或者是工程计算,在商业计算等精确计算中,要用java.math.BigDecimal(需要注意的是,在使用 BigDecimal 时,一定要将需要计算的数值作为字符串传递给构造函数,不然仍会发生精度丢失问题)。
项目中的解决方式
正如上面所述,在进行计算的时候可以通过使用BigDecimal来进行数据处理。下面举例在实际项目中对于金额的处理方式,举一个Java简单的分布式场景,如下:
用户通过产品系统发送支付指令信息将会到核心系统。核心系统进行一些列操作如增减账户余额、在数据库中保存记录明细等后通知网关,再由网关与银行进行交互。其中金额会在多个系统间流动,那么出现精度丢失是很常见的。下面提供一种解决的思路:
解决方式
对外提供时DTO对象金额类型属性采用String类型,且单位为元数据库以BigDecimal类型存储,不设小数位,且单位为分DO对象金额属性采用Long包装类型,便于计算计算时采用分作为单位来进行运算
流程:首先,核心接受到了产品发送过来的String类型的金额数值(这里对外使用String类型的原因在于前后端进行交互的场景下,当以json格式进行交互时在BigDecimal长度大于17位(不包括小数点)会出现精度丢失,在Long长度大于17位时也会出现精度丢失的问题)。接下来进行元转分操作,方式如下
public static Long convertLongMoney(String money) { BigDecimal bigMoney = new BigDecimal(money); BigDecimal feedRate = new BigDecimal(100); BigDecimal temp = bigMoney.multiply(feedRate).setScale(2, BigDecimal.ROUND_HALF_UP); Long longMoney = temp.longValue(); return longMoney; }
通过BigDecimal来进行处理后存为Long类型(可方便直接使用 +、-符合运算),通过这种方式从小数间进行运算操作变成为整数间的运算便不会出现精度丢失。计算后将数值存入以分为单位存入到数据库中。当核心需要把数据库金额提供给网关时,采用分转元形式提供,代码如下
public static BigDecimal divide2BD(BigDecimal money) { BigDecimal feedRate = new BigDecimal("100"); BigDecimal result = money.divide(feedRate, 2, BigDecimal.ROUND_HALF_UP); return result; }
转为元单位之后提供给网关系统,至此整一个处理流程例子结束。
最后
以上就是一种对于支付系统金钱数据精度丢失的解决方式,大概核心总结为以下几点
不采用float、double而采用BigDecimal进行代替运算(如果采用BigDecimal运算构造参数需要以字符串形式,否则还是有可能出现精度丢失问题)对外采用String类型,为防止前后端Json交互导致的精度丢失问题内部进行运算时先转为分进行运算,这样可以避免运算导致的精度丢失问题数据库采用BigDecimal切不设置小数为进行存储
如果有其他更好的方式欢迎朋友们提出共同讨论,如果对于以上的解决方案有更好的建议或者不足之处也欢迎提出共同讨论,共同进步。
感谢您的阅读,如果喜欢本文欢迎关注和转发,本头条号将坚持原创,持续分享IT技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步
标签: #json 精度丢失