Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions Source/NSNumber.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
# endif
#endif


#import "common.h"
#import "Foundation/NSCoder.h"
#import "Foundation/NSDecimalNumber.h"
Expand Down Expand Up @@ -708,7 +707,68 @@ - (BOOL) isEqualToValue: (NSValue*)aValue

- (NSUInteger) hash
{
return (unsigned)[self doubleValue];
NSUInteger hash;
const char type = *[self objCType];

union DoubleComponents
{
double d;
uint64_t u;
};
union FloatComponents
{
float f;
uint32_t u;
};

switch (type)
{
case 'd':
{
union DoubleComponents c;

c.d = [self doubleValue];

// Return the unsignedIntegerValue if the floating point's fractional
// component is zero.
if (c.d == floor(c.d))
{
return [self unsignedIntegerValue];
}

// Special cases. There is a positive and negative zero, make sure that
// the hashes are not different.
if (isnan(c.d) || c.d == 0.0)
{
return 0;
}

// Return the raw bit representation of the floating point.
return c.u;
}
case 'f':
{
union FloatComponents c;

c.f = [self floatValue];

if (c.f == floorf(c.f))
{
return [self unsignedIntegerValue];
}

if (isnanf(c.f) || c.f == 0.0)
{
return 0;
}

return c.u;
}
default:
hash = [self unsignedIntegerValue];
}

return hash;
}

- (NSString*) stringValue
Expand Down
54 changes: 54 additions & 0 deletions Tests/base/NSNumber/test01.m
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,60 @@ int main()
PASS([zero compare: n] == NSOrderedDescending, "zero greater than -1.01")

END_SET("zero checks")

START_SET("hashing")
// Consistency - a number's hash should be the same every time.
NSNumber *n = [NSNumber numberWithInt:42];
PASS([n hash] == [n hash], "hashing is consistent for int");
n = [NSNumber numberWithFloat:M_PI];
PASS([n hash] == [n hash], "hashing is consistent for float");
n = [NSNumber numberWithDouble:M_PI];
PASS([n hash] == [n hash], "hashing is consistent for double");

// Equality - equal numbers should have the same hash.
NSNumber *a = [NSNumber numberWithInt:42];
NSNumber *b = [NSNumber numberWithInt:42];
PASS([a hash] == [b hash], "equal int numbers have same hash");
a = [NSNumber numberWithFloat:42.0f];
b = [NSNumber numberWithDouble:42.0];
PASS([a hash] == [b hash], "42.0f and 42.0dd have the same hash");
a = [NSNumber numberWithLongLong:LLONG_MAX];
b = [NSNumber numberWithUnsignedLongLong:LLONG_MAX];
PASS([a hash] == [b hash], "LLONG_MAX and ULLONG_MAX-ish have same hash");

// Floating point numbers with zero fractional component.
a = [NSNumber numberWithDouble:42.0];
b = [NSNumber numberWithInt:42];
PASS([a hash] == [b hash], "double with zero fractional part hashes like int");
a = [NSNumber numberWithFloat:123.0f];
b = [NSNumber numberWithInt:123];
PASS([a hash] == [b hash], "float with zero fractional part hashes like int");

// Special Cases - Zero and NaN.
a = [NSNumber numberWithDouble:0.0];
PASS([a hash] == 0, "hash for 0.0 is 0");
a = [NSNumber numberWithDouble:-0.0];
PASS([a hash] == 0, "hash for -0.0 is 0");
a = [NSNumber numberWithFloat:0.0f];
PASS([a hash] == 0, "hash for 0.0f is 0");
a = [NSDecimalNumber notANumber];
PASS([a hash] == 0, "hash for NaN is 0");

// Verify different numbers have different hashes.
NSNumber *n1 = [NSNumber numberWithInt:1];
NSNumber *n2 = [NSNumber numberWithInt:2];
PASS([n1 hash] != [n2 hash], "different integers have different hashes");

NSNumber *f1 = [NSNumber numberWithFloat:1.0f];
NSNumber *f2 = [NSNumber numberWithFloat:1.1f];
PASS([f1 hash] != [f2 hash], "different floats have different hashes");

NSNumber *d1 = [NSNumber numberWithDouble:3.14159];
NSNumber *d2 = [NSNumber numberWithDouble:3.14158];
PASS([d1 hash] != [d2 hash], "different doubles have different hashes");

END_SET("hashing")

END_SET("NSNumber")

return 0;
Expand Down
Loading