Richard Topchiy February 2016

Evaluation (NSIntegers) inside if-statement Objective-C

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

Answers


Tapani February 2016

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.


Michaël Azevedo February 2016

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


NicolasMiari February 2016

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.

Post Status

Asked in February 2016
Viewed 1,229 times
Voted 13
Answered 3 times

Search




Leave an answer