【问题标题】:Why is "(2.5 < 2.5 + Number.EPSILON)" false in JavaScript?为什么 JavaScript 中的“(2.5 < 2.5 + Number.EPSILON)”为假?
【发布时间】:2019-10-19 14:32:46
【问题描述】:

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

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

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

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

【问题讨论】:

标签: javascript precision epsilon


【解决方案1】:

/*
    ┌─────────────────────────────────────┐
    │ 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))

【讨论】:

    【解决方案2】:

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

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

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

    作为一个过度暗示的示例:假设您有 5 位数字来存储一个数字。当您添加类似

      10000.
    +     0.00001
    --------------------
    = 10000.
    

    .00001 部分基本上会被“截断”,因为它不适合 5 位数字。

    (这不完全是这样的工作原理,但应该让这个想法得到理解)

    Number.EPSILON的实际值,根据the documentation,约为2.22 * 10-16,是“1与大于的最小浮点数之差1"。 (这有时称为ULP, Unit In The Last Place)。

    因此将此值添加到 1.0 将产生不同的数字。但是将它添加到 2.5 不会 得到不同的数字,因为 2.5 和大于 2.5 的最小浮点数之间的差大于这个 epsilon。因此 epsilon 被截断,就像上面示例中的 .00001


    某些语言/库可能提供“ulp”函数,该函数返回给定值与下一个更大的可表示值之间的差值。例如,在 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 &lt; 2.5 + 4.4408E-16 将是 false
    • 2.5 &lt; 2.5 + 4.4409E-16 将是 true

    【讨论】:

      【解决方案3】:

      Number.EPSILON 是:

      1 和最小浮点数之间的差值更大 大于 1

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

      现在,

      1.5 < 1.5 + 0.00000000000000000000000001 === true
      

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

      2 < 2 + 0.00000000000000000000000001 === false
      

      【讨论】:

        【解决方案4】:

        虽然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 &lt; 2.5 计算结果为 false,正如预期的那样。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-09-25
          • 2021-06-30
          • 2012-11-22
          • 2010-11-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多