为什么 JavaScript 中的“(2.5 < 2.5 + Number.EPSILON)”为假?

Why is "(2.5 < 2.5 + Number.EPSILON)" false in JavaScript?

我想在数组中查找小于特定值的值。 我尝试使用 Number.EPSILON 因为输入值不是确定值(例如 1.5000000000001)。

我在测试中发现了一些奇怪的东西:

>> (1.5 < 1.5 + Number.EPSILON) 
<- true 
>> (2.5 < 2.5 + Number.EPSILON)
<- false

这是为什么? 测试环境是Chrome浏览器控制台。

虽然 Number.EPSILON 本身可以精确表示,但这并不能保证向其添加值(或对其进行任何进一步操作)会产生完美的精度。在这种情况下,1.5 + Number.EPSILON 导致数字略高于 1.5:

console.log(1.5 + Number.EPSILON);

明显大于1.5。另一方面,将 2.5 添加到 Number.EPSILON 结果正好是 2.5 - 您希望的精度在加法过程中丢失了。

console.log(2.5 + Number.EPSILON);

正如预期的那样,

2.5 < 2.5 的计算结果为 false

Number.EPSILON 是:

difference between 1 and the smallest floating point number greater than 1

为了论证起见,假设这个数字是 0.00000000000000000000000001。

现在,

1.5 < 1.5 + 0.00000000000000000000000001 === true

而且,当你加上这个极小的分数的基数变大时,JS数学评估的精度计算就找到了它的边界。

2 < 2 + 0.00000000000000000000000001 === false

浮点数的精度有限。根据语言和体系结构,它们通常使用 32 位(float)或 64 位(double,从 "double precision" 开始)表示。尽管在 JavaScript 这样的无类型语言中事情变得模糊,但在这一切之下仍然有一台实际的机器,并且这台机器必须执行浮点运算。

问题在于,由于精度有限,某些计算结果无法准确表示。在 Wikipedia page about floating point artithmetic 上用一些例子解释了这一点。

对于想要所有 nitty-gritty 详细信息的人,通常推荐有关 What Every Computer Scientist Should Know About Floating-Point Arithmetic 的文章。但说真的:并不是每个计算机科学家都需要知道所有这些,而且我很确定世界上只有少数人真正 阅读 整件事....

作为一个过于暗示性的例子:假设你有 5 位数字来存储一个数字。当您添加

之类的内容时
  10000.
+     0.00001
--------------------
= 10000.

.00001 部分基本上是 "truncated" 因为它不适合 5 位数字。

(这并不是它的工作原理,但应该能理解这个想法)

Number.EPSILON 的实际值,根据 the documentation, is approximately 2.22 * 10-16, and is the "difference between 1 and the smallest floating point number greater than 1". (This is sometimes referred to as an ULP, Unit In The Last Place)。

因此将此值添加到 1.0 将导致不同的数字。但是加上2.5会not得到不同的数,因为2.5和大于2.5的最小浮点数的差大这个小量。因此,epsilon 被截断,如上例中的 .00001


一些 languages/libraries 可能会提供一个 "ulp" 函数,该函数 returns 给定值与下一个更大的可表示值之间的差异。例如,在 Java 中,您有

System.out.println(Math.ulp(1.0)); // Prints 2.220446049250313E-16
System.out.println(Math.ulp(2.5)); // Prints 4.440892098500626E-16

第一个显然是存储在Number.EPSILON中的内容。第二个是添加到 2.5 时应该产生不同值的值。所以

  • 2.5 < 2.5 + 4.4408E-16 将是 false
  • 2.5 < 2.5 + 4.4409E-16 将是 true

/*
    ┌─────────────────────────────────────┐
    │ Number.EPSILON = 2¯⁵² ≈ 2.2 * 10¯¹⁶ │
    └─────────────────────────────────────┘

    Question 1: 2.5 < 2.5 + ε ?
                                                        2¯⁵¹
  2¹↴                                                   ⇩
    10100000000000000000000000000000000000000000000000000  = 2.5 (JS)
   +                                                     1 = ε
   ─────────────────────────────────────────────────────────────────────
                                                        ┌┐ (01, 1 truncated )
    101000000000000000000000000000000000000000000000000001
  = 10100000000000000000000000000000000000000000000000000  = 2.5 (JS)
     ╰──────────────────── 52-bit ──────────────────────╯

    ⭐️ Conclusion ❌: 2.5 === 2.5 + ε

    Question 2: 1.5 < 1.5 + ε ?
                                                        2¯⁵²
   2⁰↴                                                   ⇩
     11000000000000000000000000000000000000000000000000000 = 1.5 (JS)
   +                                                     1 = ε
   ─────────────────────────────────────────────────────────────────────
  =  11000000000000000000000000000000000000000000000000001 = 1.5 + ε (JS)
      ╰──────────────────── 52-bit ──────────────────────╯

    ⭐️ Conclusion ✅: 1.5 < 1.5 + ε

*/

// --------------- log ---------------

const ε = Number.EPSILON;

[
    2.5 < 2.5 + ε,      // false❗️
    1.5 < 1.5 + ε,      // true

].forEach(x => console.log(x))