Justin Paulson
Justin Paulson

Reputation: 4388

Negative Integer Comparisons

I am trying to compare two integers. One is the row of an NSIndexPath, the other is the count of an NSArray. The row is equal to 0 and the count is equal to 2. I have them in an if statement as follows:

if([self.selectedSetpoint row] < ([self.theCategories count]-3))
{
    //Do true stuff
}

So by my math, the if statement should be false as I am comparing 0 < -1. However, I keep getting this statement coming through as true, and the block inside the if is being run. I have tried to NSLog the values to make sure that I am not just getting the wrong value. I placed this right BEFORE the if statement:

NSLog(@"%d",[self.selectedSetpoint row]);
NSLog(@"%d",[self.theCategories count]-3);
NSLog(@"%@",[self.selectedSetpoint row] < ([self.theCategories count]-3)?@"Yes":@"No");

and got this on the console:

2012-07-17 08:58:46.061 App[61345:11603] 0
2012-07-17 08:58:46.061 App[61345:11603] -1
2012-07-17 08:58:46.062 App[61345:11603] Yes

Any ideas why this comparison is coming up as true? Am I misunderstanding something about comparing integers?

I have another if statement just above this one that compares

[self.selectedSetpoint row]<([self.theCategories count]-2)

Which is 0 < 0, and it works fine (returns NO). So I feel like there is some issue with the use of negative integers that I am not getting.

Thank you in advance.

Upvotes: 3

Views: 2584

Answers (4)

ikuramedia
ikuramedia

Reputation: 6058

I was going to propose an alternative solution - namely to avoid using subtraction and instead use addition on the other side of the equation:

if ([self.selectedSetpoint row] + 3 < [self.theCategories count])
{
    //Do true stuff
}

This sidesteps getting caught by these kind of underflow bugs, however it leaves another gotcha untouched... Namely the conversion rules referred to in the answer to this question: What are the general rules for comparing different data types in C?

Quoting from an answer to that question, you see the C99 spec states:

  • (when one operand is signed and the other unsigned) Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

So if you have a negative value for [self.selectedSetpoint row] + 3 then the comparison will fail...

The other answers here have advocated casting (NSUInteger) to (NSInteger) - but note that this may cause overflow problems if your unsigned values are significantly massive. For e.g:

(NSInteger) -3 < (NSInteger) 4294967289 == false...

Figuring there must be an easy way to solve this, I first came up with a hard way to solve it...

#define SafeLT(X, Y) \
({ typeof (X) _X = (X); \
   typeof (Y) _Y = (Y); \
( _X < (NSInteger) 0 ? (( _Y > 0) ? YES : _X < _Y ) : ( _Y < (NSInteger) 0 ? NO : _X < _Y));})

This should work regardless of how you mix NSUInteger and NSInteger, and will ensure that the operands are evaluated at most once (for efficiency).

By way of proof of correctness:

  1. For _X < (NSInteger) 0 to evaluate to True, _X must be NSInteger and < 0, so we check if _Y > 0. The compiler will make the correct comparison here by evaluating the type of _Y. If Y > 0 then by definition we return YES. Otherwise we know both X and Y are signed and < 0 and can compare them safely.
  2. However, if X is either NSUInteger or > 0 then we test Y to see if it is < 0. If Y is < 0 then by definition we return NO. So now we have X is NSUInteger or NSInteger > 0 and Y is NSUInteger or NSInteger > 0. Since mixed comparisons will be promoted to NSUInteger we will have a safe conversion every time because there is no chance of underflow.

A simpler solution, however, would be to just typecast to a longer signed integer (iff your system has one):

#define EasySafeLT(X, Y) \
({ long long _X = (X); \
   long long _Y = (Y); \
   ( _X < _Y);})

Although this depends on having the larger type available so may not always be feasible.

Upvotes: 2

holex
holex

Reputation: 24041

the problem is the count property is kind of NSUInteger and when you subtract a higher number from a smaller you won't get a less than zero number, you will get a very large positive number, it cause the strange behaviour.

try this way, and you would get the good result:

NSLog(@"%@",(NSInteger)[self.selectedSetpoint row] < ((NSInteger)[self.theCategories count]-3)?@"Yes":@"No");

Upvotes: 0

James Webster
James Webster

Reputation: 32066

This does appear to be a case of comparing signed ints to unsigned ints. Your log statements will throw your assumptions because you are asking for the numbers to be printed signed.

NSLog(@"%d", -1); //Signed

-1;

NSLog(@"%u", -1); //Unsigned (tested on ios, 32 bit ints)

4294967295;

And then when you compare: 0 < 4294967295 certainly is true.

Casting as @ctrahey suggests should fix your problem:

if([self.selectedSetpoint row] < ( (int)[self.theCategories count] -3 ))
{
    //Do true stuff
}

Upvotes: 0

Chris Trahey
Chris Trahey

Reputation: 18290

I suspect the issue is that the return of count is an unsigned integer, and subtracting more than it's magnitude, it underflows and becomes quite large. I have run some tests, and I get the same basic behavior as you (it looks like it's -1, and in certain contexts it appears to be working as expected... however it is clearly being underflowed in the context of the if() block.

Silly problem, but luckily there is a simple solution: Cast it in place in the if statement:

if([self.selectedSetpoint row] < ( (int)[self.theCategories count] -3 ))
{
    //Do true stuff
}

Upvotes: 7

Related Questions