11

I'm a newbie at Objective-C and I'm looking for the best way to handle primitive floats and double when implementing the -hash method in an Objective-C class. I've found some good advise on isEqual and hash in general in this question:

Best practices for overriding isEqual: and hash

but it doesn't say anything on how to deal with floats and doubles.

My best attempt:

...
long lat = [[NSNumber numberWithDouble:self.latitude] longValue];
result = prime * result + (int) (lat ^ (lat >>> 32));
...

but I'm not sure this is the correct way. Any ideas ?

Community
  • 1
  • 1
Age Mooij
  • 824
  • 4
  • 14

2 Answers2

12

On the assumption that Apple's implementation of -hash is adequate, what is wrong with

result = [[NSNumber numberWithDouble: [self latitude]] hash];

Or using the modern syntax

result = [@([self latitude]) hash];
JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • 1
    Thanks, I didn't think about that one and it sure beats my version. Here's what I currently have: `result = 31 * result + [[NSNumber numberWithDouble: self.longitude] hash];` But I still wonder what the "oficial" way is to deal with doubles, since this solution does require some object creation. – Age Mooij Mar 04 '11 at 20:06
  • This is (a) type unsafe C and (b) you're not interested in the actual source value just a suitable hash, so... just use the bits. E.g. `UInt64 doubleBits = *(UInt64 *)&doubleVar;` Now you have a `UInt64` to hash and it seems you have an algorithm for that. – CRD Mar 04 '11 at 22:42
  • @CRD: How exactly is it type unsafe? It seems like a perfectly reasonable way to get a hash value to me. – JeremyP Mar 05 '11 at 18:53
  • @JeremyP: C is type unsafe so you can take a "pointer to double" and cast it to "pointer to UInt64" - a type safe language would stop you, and in general doing this should be avoided. However for the purpose of hash generation it does just what you need - gets you the bits. (Of course generating a hash for a floating point value has the same caveats as comparing them for equality, but that is another issue...) – CRD Mar 05 '11 at 23:23
  • @CRD: I wasn't casting a double pointer to a uint64_t pointer. My method was in fact completely type safe. – JeremyP Mar 06 '11 at 11:22
  • @JeremyP: Apologies, my answer wasn't clear - I was answering Age's concern in his comment that "this solution does require some object creation" and *not* saying your solution is type unsafe! It is my solution which is, but it requires no object creation which is probably good in this case. – CRD Mar 06 '11 at 17:42
8

Advice of @JeremyP about using NSNumber was pretty good, but I followed this more into depth. Since CoreFoundation is open source, I found how does CFNumber implement hashing function and this is what I found:

Split the number to integral and fraction part, hash both parts and sum them. Integral part is hashed by modulo and a hash-factor, fraction part is hashed only by multiplication.

NSUInteger HashDouble(double value) {
    double absolute = ABS(value);
    double integral = round(absolute);
    double fragment = absolute - integral;
    NSUInteger integralHash = 2654435761U * fmod(integral, NSUIntegerMax);
    NSUInteger fragmentHash = fragment * NSUIntegerMax;
    return integralHash + fragmentHash;
}

Original source code from CoreFoundation wth comments:

/* For use by NSNumber and CFNumber.
  Hashing algorithm for CFNumber:
  M = Max CFHashCode (assumed to be unsigned)
  For positive integral values: (N * HASHFACTOR) mod M
  For negative integral values: ((-N) * HASHFACTOR) mod M
  For floating point numbers that are not integral: hash(integral part) + hash(float part * M)
  HASHFACTOR is 2654435761, from Knuth's multiplicative method
*/
#define HASHFACTOR 2654435761U

CF_INLINE CFHashCode _CFHashInt(long i) {
    return ((i > 0) ? (CFHashCode)(i) : (CFHashCode)(-i)) * HASHFACTOR;
}

CF_INLINE CFHashCode _CFHashDouble(double d) {
    double dInt;
    if (d < 0) d = -d;
    dInt = floor(d+0.5);
    CFHashCode integralHash = HASHFACTOR * (CFHashCode)fmod(dInt, (double)ULONG_MAX);
    return (CFHashCode)(integralHash + (CFHashCode)((d - dInt) * ULONG_MAX));
}

From file ForFoundationOnly.h on opensource.apple.com.

Tricertops
  • 8,492
  • 1
  • 39
  • 41