js 中0.1 + 0.2 不等于 0.3 这是为什么

71 阅读2分钟

在 js 中有浮点数运算不正确的时候,比如 0.1 + 0.2 不等于 0.3 这是什么原因,要怎么解决

javascript 使用的是 IEEE-754 双精度浮点数格式表示法,浮点数在计算机中是使用二进制进行存储,呈现给用户时使用十进制数,当 0.1 和 0.2 转换成二进制时,会出现无限循环,而双精度浮点数的小数部分最多支持 52 位,超过 52 位的部分或被截断,计算机就是使用被截断后的二进制数进行计算,然后在转换成十进制数返回给用户,这个过程就已经出现误差了,而误差的部分就是被计算机截断的部分导致的

用 32 位来表示的浮点数,则称为单精度浮点数,也就是我们编程语言中的 float 变量,而用 64 位来表示的浮点数,称为双精度浮点数,也就是 double 变量

float.webp

解决方法:

  1. 使用 ES6 的 Number.EPSILON 进行误差判断,只要符合:
    Math.abs(0.2 - 0.3 + 0.1) < Number.EPSILON,则证明 0.1 + 0.2 === 0.3
  2. 获取加数中最多的小数位数 e,所有的加数同时放大 Math.pow(10, e) 倍,进行计算之后的结果再缩小 Math.pow(10, e) 倍 (0.1*Math.pow(10, 1)+0.2*Math.pow(10, 1))/Math.pow(10, 1) === 0.3 //true

Number.EPSILON 是数字中 1 和可表示的比 1 大的最小数字之间的差值,因为[双精度浮点格式]只有 52 位来表示[尾数],并且最低位的有效值为 2-52。

请注意,浮点数的绝对精度随着数字变大而降低,因为指数增长而尾数的精度保持不变。

重点是:  不要简单地将 Number.EPSILON 作为相等性测试的阈值。使用适合要比较的数字的数量级和精度的阈值。

function equal(x, y) {
  return Math.abs(x - y) < Number.EPSILON;
}

const x = 0.2;
const y = 0.3;
const z = 0.1;
console.log(equal(x + z, y)); // true
function equal(x, y) {
  return Math.abs(x - y) < Number.EPSILON;
}

const x = 1000.1;
const y = 1000.2;
const z = 2000.3;
console.log(x + y); // 2000.3000000000002;误差为 10^-13 而不是 10^-16
console.log(equal(x + y, z)); // false
function equal(x, y, tolerance = Number.EPSILON) {
  return Math.abs(x - y) < tolerance;
}

const x = 1000.1;
const y = 1000.2;
const z = 2000.3;
console.log(equal(x + y, z, 2000 * Number.EPSILON)); // true