Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong value for _.isEqual(0, new Number(Number.MIN_VALUE)) #2815

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions test/objects.js
Expand Up @@ -330,6 +330,8 @@
assert.ok(!_.isEqual(-0, 0), 'Commutative equality is implemented for `0` and `-0`');
assert.ok(!_.isEqual(null, void 0), '`null` is not equal to `undefined`');
assert.ok(!_.isEqual(void 0, null), 'Commutative equality is implemented for `null` and `undefined`');
assert.ok(!_.isEqual(0, new Number(Number.MIN_VALUE)), '`0` is not equal to `new Number(Number.MIN_VALUE)`');
dubzzz marked this conversation as resolved.
Show resolved Hide resolved
assert.ok(!_.isEqual(new Number(Number.MIN_VALUE), 0), '`new Number(Number.MIN_VALUE)` is not equal to `0`');

// String object and primitive comparisons.
assert.ok(_.isEqual('Curly', 'Curly'), 'Identical string primitives are equal');
Expand Down
2 changes: 1 addition & 1 deletion underscore.js
Expand Up @@ -1223,7 +1223,7 @@
// Object(NaN) is equivalent to NaN.
if (+a !== +a) return +b !== +b;
// An `egal` comparison is performed for other numeric values.
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
return +a === 0 && +b === 0 ? 1 / +a === 1 / b : +a === +b;
Copy link
Collaborator

@jgonggrijp jgonggrijp Apr 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just collapses the entire expression to +a === +b, since to enter the first branch, both +a and +b must already have been found to exactly equal 0. You can preserve the original fuzziness and still make the comparison symmetric by using a disjunction instead of a conjunction:

Suggested change
return +a === 0 && +b === 0 ? 1 / +a === 1 / b : +a === +b;
return +a === 0 || +b === 0 ? 1 / +a === 1 / +b : +a === +b;

It may actually be worth considering to also allow fuzziness when both values are close to zero but neither is exactly zero. This seems more consistent as it doesn't treat zero as a special case:

Suggested change
return +a === 0 && +b === 0 ? 1 / +a === 1 / b : +a === +b;
return +a === +b || 1 / +a === 1 / +b;

However, maybe there is something to say for making the comparison symmetrically exact instead of symmetrically fuzzy:

Suggested change
return +a === 0 && +b === 0 ? 1 / +a === 1 / b : +a === +b;
return +a === +b;

Copy link
Author

@dubzzz dubzzz Apr 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this is not fully equivalent. The initial check distinguishes 0 from -0 which was already the case before my change. It is not the case of the suggested change:

---        return +a === 0 && +b === 0 ? 1 / +a === 1 / b : +a === +b;
+++        return +a === +b;

As in JavaScript -0 === 0 🤔
Alternatively we can use: Object.is(+a, +b)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. So actually we have four symmetric options, from most to least fuzzy:

(1) +0 === -0 and values that approach zero from the same side are equivalent.

+a === +b || 1 / +a === 1 / +b

(2) +0 !== -0 and values that approach zero from the same side are equivalent.

+a === 0 || +b === 0 ? 1 / +a === 1 / +b : +a === +b

(3) +0 === -0, otherwise exact comparison.

+a === +b

(4) +0 !== -0, entirely like Object.is but compatible with IE.

+a === 0 && +b === 0 ? 1 / +a === 1 / +b : +a === +b

case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
Expand Down