3

I'm trying to get a random number using arc4random between -60 and 60. I generate avnumber between 1 and 120 inclusively, and then subtract 60.

If this is in one line, I get weird results whenever the number is negative.

//Loop illustrates point better.
while (1) {
    //Gets garbage numbers approximately half the time (when random1 should be negative)
    NSInteger random1 = 1 + (arc4random() %120) - 60;
    NSLog (@"random1: %li", random1);

    //Essentially same thing, but works fine.
    NSInteger random2 = 1 + (arc4random() %120);
    NSInteger random3 = random2 - 60;
    NSLog (@"random2: %li random3: %li", random2, random3);
}

Why does this happen?

Eliza Wilson
  • 1,031
  • 1
  • 13
  • 38

3 Answers3

3

I suspect the problem stems from the fact that arc4random is returning a u_int32_t (an unsigned 32-bit integer) between 0 and (2**32)-1, but doing a calculation only appropriate for signed integers.

Regardless, when generating random numbers, it's better to use arc4random_uniform, which avoids the modulo bias of your code. This bias will be barely observable, but nonetheless, you should use:

NSInteger random = 1 + (NSInteger)arc4random_uniform(120) - 60;

Or, technically, if you want numbers between -60 and 60 inclusively, you should use:

NSInteger random = (NSInteger)arc4random_uniform(121) - 60;
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Upon trial, I must un-accept your answer. While it definitely is helpful to know about that, @Sebastian's has a solution while yours doesn't. – Eliza Wilson Oct 16 '13 at 01:24
  • @jazz14456 That's fine. Do the cast if you must (like I have in my revised answer). But do not use `arc4random`. Use `arc4random_uniform`. Or, if you really want to use the `%` syntax and the bias that entails and want values between -60 and 60 (inclusive), then use 121 (e.g. `(NSInteger)arc4random() % 121 - 60`). – Rob Oct 16 '13 at 04:18
  • There is no good to reason to use arc4random for similar situations(I think) to all people who are experiencing this problem. – Eliza Wilson Oct 16 '13 at 20:54
1

arc4random() returns an unsigned integer, 1 + (arc4random() %120) - 60; is interpreted as unsigned, then assigned to NSInteger random1.

You can either cast the return value of (arc4random() %120) to a signed integer:

NSInteger random1 = 1 + (NSInteger)(arc4random() %120) - 60;

or store the intermediate result of arc4random() in an unsigned integer:

NSInteger r = (arc4random() %120);
NSInteger random1 = 1 + r - 60;


Also note that the arc4random man page recommends using arc4random_uniform() over modulo constructs:

arc4random_uniform() is recommended over constructions like ``arc4random() % upper_bound'' as it avoids "modulo bias" when the upper bound is not a power of two.

Sebastian
  • 7,670
  • 5
  • 38
  • 50
  • A typo. Should be `arc4random_uniform`, which generates random integers in a range with a uniform distribution. – Sebastian Oct 16 '13 at 21:50
0

For starters arc4random() returns an u_int32_t, unsigned 32 bit number. Unsigned numbers can not be negative so subtracting a number larger then the unsigned value results in unspecified behavior.

In the second case you assign the mod'ed value to a signed integer that can have a negative number.

As @Rob states in his answer, use arc4random_uniform instead of mod'ing to get a range in order to eliminate bias.

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
zaph
  • 111,848
  • 21
  • 189
  • 228
  • AFAIK the overflow behaviour of *unsigned* integers is well defined in c. Does objective-c differ from c here? – CodesInChaos Oct 15 '13 at 08:52
  • Objective-C is of course "C", it is a strict superset. Yes, it is well defined but at least in this case incorrect and confusing. Consider how one represents a negative number with an unsigned integer in a meaningful way. – zaph Oct 15 '13 at 11:18