2

I know to check whether 2 NSNumbers are the same you need to use ([A isEqualToNumber:B]) instead of (A == B) unlike NSIntegers.

However, I have just realized (A == B) works just fine in simulator and I'd like to know why.

The stranger thing is that on device, (A == B) STILL WORKS as long as the numbers are below 13 and from 13 it will stop working then only ([A isEqualToNumber:B]) works but that is if they're bigger than 12 otherwise (A == B) can still be used.

Why's that??

durazno
  • 559
  • 2
  • 7
  • 25
  • Are you actually using square brackets for the comparison? Basically are you doing `[a == b]` or `a == b`? – EmilioPelaez May 31 '16 at 15:05
  • Oh my bad, just fixed it but that's totally not the point... – durazno May 31 '16 at 15:08
  • I know it's not the point, I just wanted to make sure it wasn't some obscure syntax causing this. If you want to highlight code inline `like this`, you can use encapsulate your code with a ` at the beginning and one at the end. – EmilioPelaez May 31 '16 at 15:35
  • Oh I see thanks I didn't know that. – durazno May 31 '16 at 15:38

3 Answers3

3

It's an implementation detail. The == compares the addresses of objects. It just so happens that sometimes different object pointers are assigned to the same actual object when the content is the same and not mutable.

Printing the pointer values is interesting:

NSNumber *n1a = [NSNumber numberWithInt:1];
NSNumber *n1b = [NSNumber numberWithInt:1];
if (n1a == n1b) {
    NSLog(@"Match for 1");
}
NSNumber *n2a = [NSNumber numberWithInt:14];
NSNumber *n2b = [NSNumber numberWithInt:14];
if (n2a == n2b) {
    NSLog(@"Match for 14");
}
NSLog(@"1-%p 2-%p 3-%p 4-%p", n1a, n1b, n2a, n2b);

2016-05-31 11:30:49.118 TestApp[1542:3110206] 1-0x166539d0 2-0x166539d0 3-0x1656ac30 4-0x16587580

Phillip Mills
  • 30,888
  • 4
  • 42
  • 57
  • I see you've tested it on simulator but as I mentioned, why does it stop working on device when 2 numbers are bigger than 12? Why bigger than 12 specifically? So very curious. – durazno May 31 '16 at 15:36
  • 1
    No, that was running on a device. (It actually printed the "Match for 1" line and not "Match for 14".) As to why, I can only speculate: maybe they steal a few bits somewhere, maybe they have some pre-defined, commonly-used values.... – Phillip Mills May 31 '16 at 15:53
3

On NSNumber there are optimizations in work, because they are used very often.

A. Twintones

The numbers from 0 to 12 are twintones (multitones). 12 is chosen, because hours and month indexes have this range. That means, that they are reused in every creation. In pseudo code (that would not work for many reasons):

id twintones[13];

+ (instancetype)newWithInteger:(int)value
{
  if(value>=0 and value1=12)
  {
    if(!twintones[value])
    {
      twintone[value] = …; // Usual creation
    }
    return twintone[value];
  }
  // Usual creation
}

B. Tagged Pointers

Numbers are sometimes stored as tagged pointers. The basic idea is that a pointer in a 64-bit environment on OS X is always aligned at 16 byte boundaries, making the least 4 bits always 0.

abc…xyz0000

The last bit is used to mark a tagged pointer. This three unused bits left of it can be used to mark a class instead of having a full isa (class) pointer. If you store, i. e. a 3 in it (binary 011) – or whatever – after a check you can say: "That's an instance of NSNumber storing an integer". The rest of the long word can be used to store the integer value. In such a case the pointer is the object, just encoded tricky. Two equal objects have the same "pointer", but it is no real pointer.

But the most important thing: You should never take any advantage out of this optimizations. They are solely optimizations.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
  • This is the best answer here so far — exactly explains the optimizations in use, as well as the difference between numbers `0 ≤ x ≤ 12` vs. `13 ≤ x`. +1 – Itai Ferber Jun 24 '16 at 17:22
0

There are great answers here already but note that there are other optimizations at work, for example:

NSNumber *x1 = @24;
NSNumber *x2 = @24;

NSLog(@"test: %@", x1 == x2 ? @"equal" : @"different"); // prints "equal"
NSLog(@"%p", x1);
NSLog(@"%p", x2);

this will work for most numeric values because the literal values can be optimized already during compilation to result in exactly the same instance.

This (documented) optimization is often used to comparison of @YES AND @NO literals. Don't rely on it for numbers though.

Sulthan
  • 128,090
  • 22
  • 218
  • 270