Javascript 双精度浮点数计算精度问题
我们用JS进行浮点数运算时,比如0.1 + 0.2
,经常会得到一个意想不到的结果:
1 | console.log(0.1 + 0.2) // 0.30000000000000004 |
这个结果是不是很匪夷所思,那么是什么造成这个结果的呢?
原理分析
简单来说就是因为:EcmaScrpt规范定义,JavaScript中所有的数字(包括整数和小数)都只有一种类型–Number。而Number的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。它的优点是可以归一化处理整数和小数,节省储存空间。而实际计算的时候会转换成二进制计算再转成十进制。进制转换之后会很长,舍去一部分,计算再转回来,就有了精度误差。
更深度的解析请参考:JavaScript 浮点数陷阱以及解法
事实上,这个问题并不只出现在JS里,其他遵循IEEE 754标准的语言,同样存在此问题。
解决方案
这个问题既然已经存在很长时间了,那么解决方案自然也就有很多了。
官方方案
TC39 已经有一个 Stage 3 的提案 proposal bigint,大数问题有望彻底解决。在浏览器正式支持前,可以使用 Babel 7.0 来实现,它的内部是自动转换成 big-integer 来计算,这样能保持精度但运算效率会降低。
主动缩小精度范围
利用toPrecision或toFixed主动把溢出造成的细微差异过滤掉。这种方案吧,只适合浮点数,对于整数的溢出,是束手无策的。
转为整数计算
这种方案跟上面的也差不多,只适合小数。对于包含n位小数的number,先乘10的n次方,再进行计算,最后结果再除10的n次方归位
转为字符串再进行计算
这种应该是目前最普遍的解决方案了,大多数成熟计算框架都采用的此方案。优点是完全不受number存储空间的限制,可以胜任更准确更精细的计算,缺点是性能相对较差。
借助第三方计算框架
math.js
Math.js是一个用于JavaScript和Node.js的扩展数学库。它具有支持符号计算的灵活表达式解析器,大量内置函数和常量,并提供了集成的解决方案来处理不同的数据类型,例如数字,大数,复数,分数,单位和矩阵。强大且易于使用。
官网:http://mathjs.org/
中文官网:https://www.mathjs.cn/
GitHub:https://github.com/josdejong/mathjs
例
1 |
|
decimal.js
此库 与 bignumber.js相似,但是注意此库的精度是以 有效数字 来规定的,而不是小数点的位置. 此库中所有的运算都是四舍五入到 对应的精度(类似于python中的 decimal 模块),而不是仅仅只是除法运算四舍五入
官网:http://mikemcl.github.io/decimal.js/
GitHub:https://github.com/MikeMcl/decimal.js/
例
1 |
|
bignumber.js
bignumber.js是一款用于任意精度十进制和非十进制算术的JavaScript库
官网:http://alexbardas.github.io/bignumber.js/
GitHub:https://github.com/alexbardas/bignumber.js
例
1 |
|
big.js
Big.js是JavaScript的”bignumber”实现。这意味着您可以使用它以任何精度存储任何量级的数字,并在 JavaScript 中对它们执行数学运算。
官网:http://mikemcl.github.io/big.js/
Github:https://github.com/MikeMcl/big.js/
1 |
|
参考文章
深度剖析0.1 +0.2===0.30000000000000004的原因
如何解决0.1 +0.2===0.30000000000000004类问题
两分钟解惑 JS 小数计算精度问题
JavaScript 浮点数陷阱以及解法