Skip to content

NSNumber: hash by numeric value rather than truncating#695

Merged
rfm merged 1 commit into
gnustep:masterfrom
DTW-Thalion:nsnumber-hash-value
Jul 8, 2026
Merged

NSNumber: hash by numeric value rather than truncating#695
rfm merged 1 commit into
gnustep:masterfrom
DTW-Thalion:nsnumber-hash-value

Conversation

@DTW-Thalion

Copy link
Copy Markdown
Contributor

-[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.m covers 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.

-[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.
@DTW-Thalion
DTW-Thalion requested a review from rfm as a code owner July 8, 2026 18:48

@rfm rfm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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).

@rfm
rfm merged commit 37b6092 into gnustep:master Jul 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants