如何用decimal.js插件解决JS浮点数0.1+0.2≠0.3的怪异现象?

摘要:JS 中的浮点数计算经常会看到这样问题:为什么 0.1 + 0.2 不等于 0.3 ? 在浏览器控制台执行 0.1 + 0.2 会得到一个奇怪的结果:0.30000000000000004 为何结果会是这
JS 中的浮点数计算经常会看到这样问题:为什么 0.1 + 0.2 不等于 0.3 ? 在浏览器控制台执行 0.1 + 0.2 会得到一个奇怪的结果:0.30000000000000004 为何结果会是这么奇怪的一个数字?人类瞄一眼就知道的结果,为啥交给 JS 会得出这么奇怪的结果? 都知道程序的世界就是二进制的天下,在电脑的 CPU 运算时,所有的十进制都需要转为二进制计算。 十进制转二进制 十进制整数转二进制一般使用除 2 取余法,比如 35 换算二进制: 35 / 2 = 17 余数 1 17 / 2 = 8 余数 1 8 / 2 = 4 余数 0 4 / 2 = 2 余数 0 2 / 2 = 1 余数 0 1 / 2 = 0 余数 1 最终结果将余数倒序排列得到 100011 而小数换算二进制则使用乘 2 取整法,比如 0.03125 : 0.03125 * 2 = 0.0625 取整 0 0.0625 * 2 = 0.125 取整 0 0.125 * 2 = 0.25 取整 0 0.25 * 2 = 0.5 取整 0 0.5 * 2 = 1 取整 1 将最终的计算结果正序排列得到 0.00001 除了整数和小数外,还涉及到负数转换,有兴趣可以了解下 IEEE 754 标准(JS 数值运算基于 IEEE 754 标准)。 0.1 与 0.2 各种编程语言的 0.1 + 0.2 结果:https://0.30000000000000004.com/#ada 至于为什么会是 0.30000000000000004 这个结果,百度一搜一大把的文章,本文就不再赘述。 一句话总结就是 0.1 和 0.2 在转为 二进制 后是一个无限循环的结果(类似十进制中的 1/3),而 IEEE 754 标准中储存位数是有限的,在处理这种无限循环的时,会进行舍入处理,就会造成计算精度丢失,在一系列 舍入 和 规格化数 后就得出了这么一个结果。 处理办法 同一个作者,写了三个这种运算模块,npm 的周下载量都在千万级别: big.js github 地址:https://github.com/MikeMcl/big.js npm 周下载量 2 千万左右: bignumber.js github 地址:https://github.com/MikeMcl/bignumber.js npm 周下载量 1.5 千万左右: decimal.js github 地址:https://github.com/MikeMcl/decimal.js npm 周下载量: 2 千万左右。 三者区别: 官方文档:https://github.com/MikeMcl/big.js/wiki 总结: 包体积 big.js < bignumber.js < decimal.js big.js 适合基础的十进制运算,比如简单的金融计算等。 bignumber.js 支持二进制运算,适合一些加密计算场景。 decimal.js 支持二进制和三角函数运算,适合一些科学计算场景。 其他作者写的数学计算模块: https://github.com/josdejong/mathjs star 数量: 14762 npm 周下载量: 1 百万左右 瞅了一眼,好像内部也是依赖的 decimal.js 使用示例: <script src="https://registry.npmmirror.com/big.js/7.0.1/files/big.js"></script> <script src="https://registry.npmmirror.com/bignumber.js/9.3.0/files/bignumber.js"></script> <script src="https://registry.npmmirror.com/decimal.js/10.5.0/files/decimal.js"></script> <script> (() => { // 加法 console.log('原生 JS 计算:', 0.1 + 0.2) // 0.30000000000000004 // 减法 console.log('原生 JS 计算:', 0.3 - 0.1) // 0.19999999999999998 // 乘法 console.log('原生 JS 计算:', 0.2 * 0.1) // 0.020000000000000004 // 除法 console.log('原生 JS 计算:', 0.3 / 0.1) // 2.9999999999999996 })(); (() => { const x = new Big(0.1) const y = new Big(0.2) const z = new Big(0.3) // 加法 console.log('big.js 插件计算:', x.plus(y).toNumber()) // 0.3 console.log('big.js 插件计算:', x.plus(y).toString()) // '0.3' // 减法 console.log('big.js 插件计算:', z.minus(x).toNumber()) // 0.2 // 乘法 console.log('big.js 插件计算:', x.times(y).toNumber()) // 0.02 // 除法 console.log('big.js 插件计算:', z.div(x).toNumber()) // 3 })(); (() => { const x = new BigNumber(0.1) const y = new BigNumber(0.2) const z = new BigNumber(0.3) // 加法 console.log('bignumber.js 插件计算:', x.plus(y).toNumber()) // 0.3 console.log('bignumber.js 插件计算:', x.plus(y).toString()) // '0.3' // 减法 console.log('bignumber.js 插件计算:', z.minus(x).toNumber()) // 0.2 // 乘法 console.log('bignumber.js 插件计算:', x.times(y).toNumber()) // 0.02 // 除法 console.log('bignumber.js 插件计算:', z.div(x).toNumber()) // 3 })(); (() => { const x = new Decimal(0.1) const y = new Decimal(0.2) const z = new Decimal(0.3) // 加法 console.log('decimal.js 插件计算:', x.plus(y).toNumber()) // 0.3 console.log('decimal.js 插件计算:', x.plus(y).toString()) // '0.3' // 减法 console.log('decimal.js 插件计算:', z.minus(x).toNumber()) // 0.2 // 乘法 console.log('decimal.js 插件计算:', x.times(y).toNumber()) // 0.02 // 除法 console.log('decimal.js 插件计算:', z.div(x).toNumber()) // 3 })(); </script> 也可以使用 npm 安装使用 // npm install big.js // const Big = require('big.js'); import Big from 'big.js'; const x = new Big(0.1) 原生 JS 要处理浮点数计算也能做,只是比较麻烦,需要将计算的小数转为整数之后再进行计算,最后除以倍数。比如 0.1 + 0.2 可以转为 (1+2) / 10。 JS 支持的 最大整数 也是有边界的,在 −2的53次方 + 1 到 2的53次方 − 1 之间(即 -9007199254740991 到 9007199254740991)。 超出这个边界计算也会出问题,比如: (() => { console.log('超出边界计算', 2**53) // 9007199254740992 console.log('超出边界计算', 2**53 - 1) // 9007199254740991 console.log('超出边界计算', 2**53 - 1.1) // 9007199254740991 console.log('超出边界计算', 2**53 + 1) // 9007199254740992 console.log('超出边界计算', 2**53 + 1.1) // 9007199254740994 })(); 超大数计算也可以使用 big.js 处理: (() => { const x = (new Big(2)).pow(53) const y = new Big(1.1) console.log('加法超出边界计算', x.plus(y).toString()) // 9007199254740993.1 console.log('减法超出边界计算', x.minus(y).toString()) // 9007199254740990.9 })(); 写在最后 凡使用 IEEE 754 标准的编程语言,都存在浮点数计算问题,只是其他语言有内置的解决方案,而 JS 最初的设计思想是用于浏览器交互,所以没有内置解决方案,好在有热心作者开源的插件用于解决浮点数计算问题。 在使用 JS 计算时,需特别小心浮点数问题,能交给后端处理就交出去~~