有时需求中会有前端校验输入数字金额的时候,判断,几个输入框的金额合计是否大于小于或等于某个整数,在输入的值可以为小数的时候,很容易就出现js小数点计算丢失精度问题。比如下图
js高级程序设计(我这版是第3版)在3.4.5Number类型这节中就谈到了这个现象,原话是:
关于浮点数值计算会产生摄入误差的问题,有一点需要明确:这是使用基于IEEE754数值的浮点计算的通病,ESMAScript并非独此一家,其他使用相同数值格式的语言也存在这个问题。
所以即使浮点数值的最高精度是17位小数,但在进行算术计算时其精度远远不如整数。
1.使用toFixed(x)方法,x为必需,规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,如果省略了该参数,将用 0 代替。
比如:
但是这种方法的局限性是不能使用toFixed(x)去进行舍入操作。因为IEEE754标准规定的浮点数取整算法是银行家舍入法,即四舍六入五留双法:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。
早期的时候似乎在不同浏览器会有不同的表现,但是现在基本上大部分浏览器都是toFixed(x)遇5不会进1,而是直接舍去,可能也和chrome内核一家独大有关系。在Chrome浏览器中的控制台表现如下:
精度只能用于所有操作数中最多小数位的精度计算,即toFixed(x)中的x必须大于等于最多小数位操作数的小数位数量,否则就会丢失精度。
2.使用第三方库
比如Math.js,decimal.js,使用方法大同小异,详情见:
https://www.npmjs.com/package/mathjs
https://www.npmjs.com/package/decimal.js/v/3.0.0
以vue2.x为例:
先npm install mathjs / decimal.js
然后在main.js中引入,以ES模块规范为例:
math.js是这样使用的:(详见math.js文档:https://mathjs.org/docs/index.html)
import * as math from \"mathjs\";const config = {epsilon: 1e-12,matrix: \'Matrix\',number: \'number\', //运算时需要精度准确时此处需配置为BigNumberprecision: 64, //仅在number类型为BigNumbers生效predictable: false,randomSeed: null //选项设置为种子伪随机数生成,使其成为确定性的。}const math = create(all, config);let number = math.add(math.bignumber(0.1), math.bignumber(0.2)); //操作数中至少要有一个调用bignumber()
decimal.js是这样使用的:(详见decimal.js文档:http://mikemcl.github.io/decimal.js/)
import { Decimal } from \'decimal.js\';let number = new Decimal(0.11).add(new Decimal(0.29));
其他
本来到前面就到尾声了,但是这里还是需要啰嗦一下,我还看过一些博客还提到一个方法或是自己封装的方法,其核心是先将小数*10的n次方放大为整数,只要最终结果在Number类型边界之内的整数运算是不会有精度误差的,然后再将整数的运算结果除于10的n次方就得到最终的结果。但是,这个方法没写好也是存在问题的,就比如前面举的例子:0.07*100,结果就不是7。因为只有少数几个小数可以被浮点数精确表示,比如0.25的N次方,或是0.75的n次方,其他的小数基本都是近似数。其实我也很纳闷0.06,0.08也都是近似数,但是他们相乘100结果分别就是6,8。有些博主通过这个方法封装的方法都没考虑到这块,所以在相乘放大过程中就已经出错了,但是如果在放大缩小过程中能控制住精度,也仍不失为一个解决问题的好方法。