2

I am in doubt, why this work correctly:

NSInteger row = indexPath.row;
NSInteger preloadTrigger = self.nodes.count - 20;
if (row >= preloadTrigger) {
    [self.loader loadNextposts];
}

And this does not (just skips the if-statement):

if (indexPath.row >= self.nodes.count - 20) {
    [self.loader loadNextposts];
}

when the value of self.nodes.count - 20 is negative.

However, when the value of the expression is positive, it works fine always.

A very strange behavior, as I cannot see semantic difference in two expressions.

Update: So, I decided to test it:

(lldb) po self.nodes.count - 20
18446744073709551601

(lldb) po preloadTrigger
-15
Richard Topchii
  • 7,075
  • 8
  • 48
  • 115

3 Answers3

3

According to Apple Docs, count property is a NSUIntegerin objective-C.

When you write :

 NSInteger preloadTrigger = self.nodes.count - 20;

In fact you are casting count to a NSInteger object, and can have a negative value if count isn't greater than 20.

But when you write :

(indexPath.row >= self.nodes.count - 20)

count is a NSUInteger object, and subtract 20 from it will always lead to a positive number (a huge one by the way).

Michaël Azevedo
  • 3,874
  • 7
  • 31
  • 45
2

Because nodes.count is NSUInteger and row is NSInteger. Unsigned integer - 20 is never a negative value, but results a huge positive value where you expect it to be negative.

Tapani
  • 3,191
  • 1
  • 25
  • 41
  • I though maybe the compiler might promote the it to signed because he is subtracting the (seemingly signed) literal `20`... – Nicolas Miari Feb 08 '16 at 08:43
  • Why would the compiler make such an assumption that 20 is signed when it is used with an unsigned property? – Tapani Feb 08 '16 at 08:44
  • This is certainly the issue, but it's still not clear why the two code fragments differ. Please elaborate. – trojanfoe Feb 08 '16 at 08:45
  • @Tapani I don't know. But I know you can specify an unsigned literal with `20u`, so I assumed that `20` defaults to **signed**. Then, an operation involving `(unsigned) - (signed)` ought to promote the first operand, to be safe (again, not sure if that's how ints work) – Nicolas Miari Feb 08 '16 at 08:45
  • Because when you put the unsigned value to NSInteger variable the unsigned huge value does not fit into the integer's positive range and becomes unsigned. – Tapani Feb 08 '16 at 08:46
  • Indeed, the literal `20` (_not_ `20u` or `20U`) is of type (signed) `int`: https://www.cs.princeton.edu/courses/archive/fall13/cos217/precepthandouts/03simplepgms/datatypes.pdf – Nicolas Miari Feb 08 '16 at 08:53
  • 1
    Now I see _what_ is going on: The promotion goes in the other direction (the signed int gets "promoted" to uint), so `count - 20` is unsigned (and wrapped if `count` was smaller than 20!). It makes sense: uint has a wider range (if only in the positives). Source: http://stackoverflow.com/a/28133443/433373 – Nicolas Miari Feb 08 '16 at 08:55
0

I'll add some explanation to the other, correct answers.

So, here is how it goes:

self.nodes.count is of type NSUInteger, which is the same as unsigned long int in 64 bit systems, or alternatively unsigned int in 32 bit systems.

The literal 20 is of type int.

When you form the expression self.nodes.count - 20, 20 is 'promoted' to the unsigned integer type of the other operand (self.nodes.count), because it has the wider range of the two.

That is because, when both operands have types of different sizes, the smaller one gets promoted to the larger one to make them equal and calculate the result in those terms (in hardware, arithmetical operations between values of different types aren't really defined - the bit representations differ).

The problem is that, in exchange for being able to represent a wider range of positive values with the same bit length, unsigned integers can not represent negative values. So, when 20 is greater than self.nodes.count, the result "wraps around" to a large, unsigned integer.

On the other hand indexPath.row, too, is an unsigned integer (NSUInteger), so you end up comparing the relatively small row value with the huge result of the subtraction operation; the test:

if (indexPath.row >= self.nodes.count - 20)

...always fails (the left side is smaller).

If you first cast both results to signed integer and then compare those signed integers:

NSInteger row = indexPath.row;
NSInteger preloadTrigger = self.nodes.count - 20;
if (row >= preloadTrigger) {

...then no wrapping/underflow occurs and you get the expected result.

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189