JS四则运算与四舍五入精度问题及解决方案

5,259 阅读5分钟

一、Javascript精度问题业务背景

JS中 0.1+0.2 = 0.3000000000000004的问题,在很多业务场景里都是一个令人头痛的问题。尤其是在大型的电商企业,货币基金股票行业的网页中,JS四则运算和toFixed精度问题更是让人防不胜防。 京东曾经发生过一起线上toFixed精度问题,差点酿成大错: 京东 虽然看起来只有0.01的差别,但是如果消费者用来投诉甚至打官司,这完全可以成为欺诈消费者的理由。原因就是JS里“该死”的toFixed方法。

二、剖析Javascript精度问题原因

1、超大数和浮点数四则运算精度问题原因

在剖析JS精度问题之前,要简单描述下JS计算的方式背景

1、在JS内部所有的计算都是以二进制方式计算的。

2、JS内部无法无限制保存二进制数值的长度。(最长52位)

这两点其实都不难理解。计算机底层都是0和1,当然,计算机也不能保留无限长(无限大)的东西。

知道了这两点,那么自然也就不难理解为什么JS在计算超大的数值的时候,会出现问题了,就好像你永远无法算清宇宙里有多少颗星星一样,计算机也不行。

那么为什么计算很小浮点数的时候也会出问题呢,答案就是,在JS内部,浮点数也是用很长很长的二进制表示的(具体为什么请参考计算机原理)。

在JS的世界里,0.1转换为二进制是0.00011001100110011…你会发现这串数字是无数个0.11在循环..(0.0(0011)(0011)(0011)(0011)…还有无数0011)没办法,在JS里,浮点数就是转化为无穷长的二进制,所以JS只能截取这串二进制的前52位进行二进制的相加,那么下面不用再说了,自然就会出现精度问题。

这也可以解释,为什么JS中整型的数值加减不会出现问题,但是超大数值和浮点数计算会出现问题。

2、toFixed()精度问题

toFixed问题其实很简单,就是浏览器的坑。 图片描述

图片描述

可以看到,在IE和chrome两个浏览器下面,一个简单的四舍五入,显示的结果完全不同。就是因为不同的浏览器厂商对于四舍五入没有统一的标准,就让我们这些开发人员踩坑。。 在IE里,toFixed就是采用常规的四舍五入方法,然而,在chrome中,toFixed采用了更为精确的算法。查阅一些资料后,经过官方查证toFixed 原生API如下:

toFixed Api 大概意思就是如果toFixed的入参小于10的21次方,那么就取一个整数n,让n*10^f - x 的精确值尽可能的趋近于0,如果存在两个这样的n,取较大的n。

toFixed 上图例子中1.335.toFixed(2)按照四舍六入五成双应该是1.34,但是实际情况确实1.33。而用官方的API就可以解释为什么是1.33,因为n=133的时候让n*10^f - x更趋近于0(虽然结果又出现了四则运算的精度问题,但是确实是符合规则的),所以最后得到的结果是1.33。

这个方法经过10几次toFixed验证后发现都是和结果相吻合的,应该是正确的问题根源所在。

(PS:该错误对解决方案无影响)

三、精度问题终极解决方案。

1、四则运算精度问题解决方案

前面提到JS中整型运算是没有问题的,那么把浮点数扩大相应的整数倍,转化成整型在进行计算,计算完缩小为整数倍不就可以解决这个问题了么。 这个方案确实是可行的,然鹅,怎么实施这个方案,却需要琢磨下。 首先第一想到的是乘法,512.06*100就可以转化为 51206了,然鹅。。 图片描述 有点坑,因为浮点数的计算本身就会有问题,显然想简单的通过乘法来实现整型转化,是不靠谱的,这时候就要考虑自己实现一个万无一失的整型转化方法。 将数字当做字符串,手动的进行小数点移位,这个方案把浮点型转换为整型是完全可行的,接下来就考虑怎么实现的问题。 因为在JS里,超过一定大小的number就会被JS自动转化为科学计算法,所以在考虑把number当成字符串处理时。一定要考虑到这一点。

解决方案的思路就是这样,具体的代码写起来很相对比较复杂,我参考网上的function自己整合了一个npm包,需要的朋友可以自己查看下源码。 总体的思路就是上文介绍的,具体代码实现可以参考源码(如果能顺便点个赞,那最好啦)

2、toFixed解决方案

toFixed的方案相对要好解决,只要有了精度没问题的四则运算,四舍五入只要直接判断下浮点数需要精确位数是否大于5即可,具体的代码实现也可以参考源码

四、总结

解决方案核心的点就在于用字符串方式来解决小数点精度问题,虽然实现上有点复杂,需要考虑的情况比较多,但是确实是最稳妥的做法。如果有需要高精度业务场景的小伙伴可以直接install我封装的npm包,就可以直接用啦,教程参考github哦!

最后,如果这篇文章对你有点点帮助的话,欢迎动动鼠标点个赞哦!

(纯属个人手动打字,有手误或者技术错误的地方,肯定多多指正!)