NSNumber: hash by numeric value rather than truncating#695
Merged
Conversation
-[NSNumber hash] cast the doubleValue to an integer, so every value with the same integral part collided (1.5 and 1.7 hashed alike). Reduce the doubleValue modulo a Mersenne prime instead, after the manner of CPython, so the fractional part contributes to the hash and an integral value still hashes to that integer. The hash remains a function of doubleValue, which is what -compare: and -isEqual: use, so equal numbers held in different types hash equally.
rfm
approved these changes
Jul 8, 2026
rfm
left a comment
Contributor
There was a problem hiding this comment.
Great, Given that this is the approach I found looked best when I searched for alternative hashing solutions, I have nothing to complain about here. I know Hugo was going to come back after reviewing the literature, but this is a good solution (and if he finds a better one we can always change later).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
-[NSNumber hash]cast doubleValue to an integer, so every value sharing an integral part collided: 1.5 and 1.7 hashed alike.This reduces doubleValue modulo a Mersenne prime, after the manner of CPython, so the fractional part contributes and an integral value still hashes to that integer.
The hash stays a function of doubleValue because that is what
-compare:and-isEqual:use. NSNumber equality promotes to double, so a value held as int, float or double compares equal across those types and must hash equally. Hashing the raw storage, as #535 first tried, breaks that: the bit patterns of an int and a double of the same value differ, so equal numbers get different hashes and fail as dictionary keys.One consequence worth noting: equality is not transitive across the precision boundary. A long long above 2^53 compares equal to the double it rounds to, so its hash must be that of the double, and distinct large integers can collide. That follows
-compare:and cannot be avoided without changing comparison.Tests/base/NSNumber/NSNumber_hash.mcovers the-hash/-isEqual:contract, fractional scattering and non-finite values. 862 tests pass under both the GNU and libobjc2 runtimes.This is an option for #535, following the doubleValue-based approach discussed there.