-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
base: master
Are you sure you want to change the base?
Conversation
6a64ee2
to
f64b8e8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dubzzz You are assuming that
_.isEqual(0, new Number(Number.MIN_VALUE)) === true
is wrong, but the code seems to suggest that this was intended (“zero is equal to very close to zero”). Given the division that converts both operands to Infinity
, it might actually be
_.isEqual(new Number(Number.MIN_VALUE), 0) === false
that is wrong instead.
Although the original author of EGAL
(see here, section 3B, “Equality of Symbols and Numbers”), the algorithm that the comment in the source refers to, actually seems to agree with you that zero is not equal to very close to zero.
In both cases, however, you are right to point out that the comparison is asymmetric. I agree that it shouldn’t.
This seems like a unique opportunity for Underscore to make a choice. Currently, it is fuzzy in one direction but exact in the other direction. It should probably behave the same in both directions: fuzzy or exact?
@jashkenas What do you think? I would vote for exact, partly because it is less code and partly because this smells like a special case for zero (although one of my suggestions below fixes that in a way that preserves fuzziness and also reduces the amount of code). If choosing fuzzy, the comment probably shouldn't refer to EGAL
, because EGAL
seems to be exact.
@@ -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; |
There was a problem hiding this comment.
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:
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:
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:
return +a === 0 && +b === 0 ? 1 / +a === 1 / b : +a === +b; | |
return +a === +b; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
I’d be happy to defer to whichever version you think is best for this, because I don't have a particularly strong opinion about how this should behave, in the context of JavaScript floating point ubiquity. But if we do decide to make a change here, it would be a breaking change. |
Co-Authored-By: Julian Gonggrijp <[email protected]>
@jashkenas IMO as the original source code was considering What's your opinion? If it can help for the decision both Jasmine and Jest opted to distinguish |
I made a new comment above that numbers four options from most to least fuzzy. The current code behaves like option (2) or (3) depending on the values of the operands and the order in which they are fed into If choosing option (1) or (4), maybe it should also be considered a semantic change. Option (4) is what @dubzzz is currently proposing. Edit to add: option (4) is probably the most |
Thanks for laying out the tl;dr of the four options. It sounds like it might be time to have "the big semver conversation" if we go with option 4... |
I think I agree with @dubzzz that option 4 is the most correct, but it is not a strong opinion. As for semver, we could work with parallel 1.x and 2.x branches for a while so we don't have to rush the 2.0 release. One of those branches could be I'm going to compose an email to you because there's something I'd like to bring to the table for a potential 2.0. |
The current implementation of underscore is returning an invalid answer for:
Similar to a bug found in Jest jestjs/jest#7941
This bug has been discovered with the following test - property based testing test:
Please note that such kind of tests are currently used within Jest to avoid future issues.