了解下 Number.EPSILON 吧!

604 阅读2分钟

Number.EPSILON

Number.EPSILON 是 JavaScript 的静态属性,返回 1 与大于 1 的最小可表示数的差值。 Number.EPSILON 在数学中用于表示任意小(极小)的正数,符号为 ε。在计算机中用于衡量浮点数的相对精度,也被称之为“机器 Epsilon(Machine Epsilon)”。

机器 Epsilon

由于浮点数在计算机中的存储位数有限,因此在表示时也会存在精度限制。而机器 Epsilon 便是用来衡量不同标准下的浮点数,在表示数字时能够达到的最小精度,其值规定为大于 1 的最小可表示数和 1 的差

以 IEEE754 双精度浮点型为例,比 1 大的最小可精确表示的数,其形式当为 1.000000....1,由于双精度浮点型尾数只有 52bit,因此在除了隐含的最高有效位 1 之外,实际尾数前 51 位都为 0,最后第 52 位则为 1,用浮点数格式表示则为:

0 01111111111 0000000000000000000000000000000000000000000000000001

符号位为 0,指数位为 0,尾数部分隐含最高有效位 1。为了便于书写也可以变形为如下式子:1+2521+2^{-52},在计算机器 Epsilon 值时,只需要用这个式子减去 1 即可。

(1+252)1=2522.220446049250313E16(1+2^{-52})-1 = 2^{-52} \approx 2.220446049250313E-16

下面是 2522^{-52} 转换为浮点数的格式表示:

0 01111001011 0000000000000000000000000000000000000000000000000000

符号位为 0。 尾数位规范为 1.xxx...xxx 的形式,因此小数点像左移动 52 位,又因规格化数尾数存在隐含的最高有效位 1,因此省略不写,故实际尾数全为 0。 指数位为 -52,用移码表示后其阶码为 52+1023=971(0b01111001011)-52+1023=971 (0b01111001011)

衡量精度

机器 Epsilon 的值用于衡量浮点数系统中两个相邻可表示的浮点数之间的最小间隔,间隔越小,则精度越高,通常取值为 1 与大于 1 的最小可表示数的差,在双精度浮点型中通常为 2522^{-52},因此 Number.EPSILON 实际上就是 JavaScript 能够表示的最小精度。

为什么不能使用 Number.MIN_VALUE 作为最小精度?它的值远远小于 Number.EPSILON,近似为 53245^{-324}。这是因为 Number.MIN_VALUE 是浮点型能够表示的最小非规格化正数,它代表了当前浮点数系统所能表示的数值下限,主要用于平滑下溢到 0,故不能用于反应两个相邻可表示的浮点数的最小间隔。

近似相等

由于浮点数的运算可能会产生舍入误差,因此不能直接与浮点数的运算结果进行全等 === 判断,此时机器 Epsilon 便可以作为一个合理的误差阈值,来判断两个浮点数是否在可接受的误差范围内近似相等,如果两个浮点数的差值小于机器 Epsilon ,则可以认为误差不存在。

function equal(a, b) {
	return Math.abs(a - b) < Number.EPSILON;
}
equal(0.1 + 0.2, 0.3); // true

数字越大,精度越低,这是因为小数尾数的位数固定不变,但是指数的增长,会导致小数点的位置远大于尾数的实际长度,从而意味着同样的 52 位尾数需要表示更大范围的数值,能够表示的精度就会降低。

Number.EPSILON 默认上反应了数字 1 附近的最小精度,可以看作是 Number.Epsilon * 1。对于一些更大数量级的浮点运算,比如 10210^2 级,就不合适了,这是因为在 10210^2 附近的精度因为尾数位占用的原因,要远大于默认的 Number.EPSILON,其精度也就变的更低,此时我们应该要给于一个更大的容差范围。

根据数量级来自动调整阈值

通常,这个容差范围的合理取值是取参与运算的浮点数中的最大数的机器 Epsilon 倍数,也就是根据数量级来自动调整阈值:

  function equal(a, b) {
	const maxNum = Math.max(Math.abs(a), Math.abs(b));
	const epsilon = Number.EPSILON * maxNum;
	return Math.abs(a - b) < epsilon;
  }

使用绝对误差阈值

除了按数量级自动调整外,在可以特定步长输入的场景中,例如 <input type="number" step="0.1" />,输入的值只能以最小 0.1 步长增加,此时容差的范围就可以允许更大,精度也就可以更低,例如使用绝对误差阈值,将最小精度固定为 0.01 ,并不使用 Number.EPSILON,这也是可以的。