number精度问题(如:0.14*100出现多位小数)

常见的例子有以下:

// 加法 =====================
0.1 + 0.2 = 0.30000000000000004
0.7 + 0.1 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001

// 减法 =====================
1.5 - 1.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998

// 乘法 =====================
19.9 * 100 = 1989.9999999999998
0.8 * 3 = 2.4000000000000004
35.41 * 100 = 3540.9999999999995

// 除法 =====================
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999

产生精度问题的原因

和其它语言如 Java 和 Python 不同,JavaScript 中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用 64 位固定长度来表示,也就是标准的 double 双精度浮点数(相关的还有float 32位单精度)。底层计算的时候,会将十进制转化为二进制后,再进行计算。计算完后再转化为十进制。

十进制与二进制的转化方式

十进制转化二进制

拿 173.8125 举例如何将之转化为二进制小数。 1. 针对整数部分 173,采取除 2 取余,逆序排列; 173 / 2 = 86 ... 1 86 / 2 = 43 ... 0 43 / 2 = 21 ... 1 ↑ 21 / 2 = 10 ... 1 | 逆序排列 10 / 2 = 5 ... 0 | 5 / 2 = 2 ... 1 | 2 / 2 = 1 ... 0 1 / 2 = 0 ... 1 复制代码得整数部分的二进制为 10101101。 2. 针对小数部分 0.8125,采用乘 2 取整,顺序排列; 0.8125 2 = 1.625 | 0.625 2 = 1.25 | 顺序排列 0.25 2 = 0.5 | 0.5 2 = 1 ↓ 复制代码得小数部分的二进制为 1101。 3. 将前面两部的结果相加,结果为 10101101.1101;

此外js可以直接通过变量调用toString(2),将十进制转化为二进制字符串,再用Number()转化为数值。

二进制转化十进制

就是把一个有X位的二进制数,从右往左数,每一位依次乘以2的0次方,2的1次方,2的2次方,一直乘到2的X-1次方,然后把这些次方的结果加起来即可得到最终一个十进制数的结果。例如求一个5位的二进制数10110要转为十进制数: 02^0 + 12^1 + 12^2 + 02^3 + 1*2^4 = 22

js可以通过parseInt(*,2)的方式将二进制转化为十进制

解决方案

数据展示类

当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:

parseFloat(1.4000000000000001.toPrecision(12)) === 1.4  // True

封装成方法就是:

function strip(num, precision = 12) {
  return +parseFloat(num.toPrecision(precision));
}

为什么选择 12 做为默认精度?这是一个经验的选择,一般选12就能解决掉大部分0001和0009问题,而且大部分情况下也够用了,如果你需要更精确可以调高。

数据运算类

对于运算类操作,如 +-*/,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算。以加法为例:

/**
 * 精确加法
 */
function add(num1, num2) {
  const num1Digits = (num1.toString().split('.')[1] || '').length;
  const num2Digits = (num2.toString().split('.')[1] || '').length;
  const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
}

以上方法能适用于大部分场景。遇到科学计数法如 2.3e+1(当数字精度大于21时,数字会强制转为科学计数法形式显示)时还需要特别处理一下。

相关开源有:Math.js、BigDecimal.js

理解

所谓的精度问题并不是真的有问题,而是将运算值展示得更为精确。js本身只有一个number类型,默认就是64位双精度的,两个数值通过计算后,结果作为一个新的变量,展示就是按照默认的精度展示。

参考

掘金-探寻 JavaScript 精度问题以及解决方案 github-JavaScript 浮点数陷阱及解法

Last updated

Was this helpful?