Wakeup
Wakeup

Reputation: 165

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

I want to find values less than a certain value in the array. I tried to use Number.EPSILON because the input value is not a definite value (eg 1.5000000000001).

I found something strange during the test:

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

Why is this? The test environment is the Chrome browser console.

Upvotes: 13

Views: 1442

Answers (4)

lochiwei
lochiwei

Reputation: 1358


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

Upvotes: 0

Marco13
Marco13

Reputation: 54659

Floating point numbers have a limited precision. Depending on the language and architecture, they are usually represented using 32 bits (float) or 64 bits (double, as of "double precision"). Although things become blurry in an untyped language like JavaScript, there is still an actual machine underneath all this, and this machine has to perform floating point arithmetic.

The problem is that the results of certain computations cannot be represented accurately, given the limited precision. This is explained with some examples on the Wikipedia page about floating point artithmetic.

For people who want all the nitty-gritty details, the article about What Every Computer Scientist Should Know About Floating-Point Arithmetic is usually recommended. But seriously: Not every computer scientist needs to know all this, and I'm pretty sure that only a handful people in the world have actually read the whole thing....

As an overly suggestive example: Imagine you had 5 digits to store a number. When you then have an addition like

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

the .00001 part will basically be "truncated" because it does not fit into the 5 digits.

(That's not exactly how this works, but should get the idea across)

The actual value for Number.EPSILON, according to 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).

So adding this value to 1.0 will result in a different number. But adding it to 2.5 will not result in a different number, because the difference between 2.5 and the smallest floating point number greater than 2.5 is larger than this epsilon. The epsilon is thus truncated, like the .00001 in the example above.


Some languages/libraries may offer an "ulp" function that returns the difference between a given value and the next larger representable value. For example, in Java, you have

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

The first one obviously is what is stored in Number.EPSILON. The second one is the value that should yield a different value when added to 2.5. So

  • 2.5 < 2.5 + 4.4408E-16 would be false and
  • 2.5 < 2.5 + 4.4409E-16 would be true

Upvotes: 5

Charlie
Charlie

Reputation: 23818

Number.EPSILON is:

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

Let's say this number is 0.00000000000000000000000001 for the argument's sake.

Now,

1.5 < 1.5 + 0.00000000000000000000000001 === true

and, when the base number which you are adding this extremely small fraction grows larger, the precision calculations of the JS math evaluation finds its boundary.

2 < 2 + 0.00000000000000000000000001 === false

Upvotes: 0

CertainPerformance
CertainPerformance

Reputation: 370989

While Number.EPSILON itself can be represented precisely, this is no guarantee that adding values to it (or manipulating it any further at all) will result in perfect precision. In this case, 1.5 + Number.EPSILON results in a number slightly higher than 1.5:

console.log(1.5 + Number.EPSILON);

Which is clearly greater than 1.5. On the other hand, adding 2.5 to Number.EPSILON results in exactly 2.5 - the precision you were hoping for was lost during the addition process.

console.log(2.5 + Number.EPSILON);

2.5 < 2.5 evaluates to false, as expected.

Upvotes: 7

Related Questions