Strange behaviour of rand() in Xcode

懵懂的女人 提交于 2021-02-16 14:59:52

问题


While using Apple Xcode, the C program I include below will always return a 5 for y but z will return a random number, even though they have the same expression. Can you identify a reason for this behaviour?

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main()
{
    int x,y,z;
    srand((unsigned int)time(NULL));
    x = ((unsigned int)time(NULL));
    y=((double)rand()/(double)(RAND_MAX)*10);
    z=((double)rand()/(double)(RAND_MAX)*10);
    printf("Time %d\n", x);
    printf("\n");
    printf("Number y %d\n", y);
    printf("\n");
    printf("Number z %d\n", z);
    printf("\n");
    return 0;
}

回答1:


rand-31

Here's an adaptation of your program which shows more detail of what's going on:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    int x = ((unsigned int)time(NULL));
    srand((unsigned int)x);
    int r1 = rand();
    int y  = ((double)r1/(double)(RAND_MAX)*10);
    int r2 = rand();
    int z  = ((double)r2/(double)(RAND_MAX)*10);
    printf("Time %d\n", x);
    printf("Random 1: %d\n", r1);
    printf("Number y: %d\n", y);
    printf("Random 2: %d\n", r2);
    printf("Number z: %d\n", z);
    return 0;
}

It captures the result of the two rand() calls independently of the calculation so that they can be printed. It also ensures that the value passed to time() is the same as the value printed, though the chances of them being different are pretty small.

When I ran it a few times, I got the outputs:

Time 1508694677
Random 1: 1292016210
Number y: 6
Random 2: 1709286653
Number z: 7

Time 1508694685
Random 1: 1292150666
Number y: 6
Random 2: 1821604998
Number z: 8

Time 1508694701
Random 1: 1292419578
Number y: 6
Random 2: 2046241688
Number z: 9

Time 1508694841
Random 1: 1294772558
Number y: 6
Random 2: 790587255
Number z: 3

As you can see, the values returned by rand() are different, but not very different, and consequently the computed value isn't all that different.

rand-23

One easy upgrade option is to use the drand48() functions, like this:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    int x = ((unsigned int)time(NULL));
    srand48((unsigned int)x);
    int r1 = lrand48();
    int y  = ((double)r1/(double)(RAND_MAX)*10);
    int r2 = lrand48();
    int z  = ((double)r2/(double)(RAND_MAX)*10);
    printf("RAND_MAX: %d\n", RAND_MAX);
    printf("Time:     %d\n", x);
    printf("Random 1: %d\n", r1);
    printf("Number y: %d\n", y);
    printf("Random 2: %d\n", r2);
    printf("Number z: %d\n", z);
    return 0;
}

The code prints RAND_MAX mainly to show that it is compatible with the range of values returned by lrand48(), which is documented to return a value in the range 0..231-1.

This gives different and arguably better results:

RAND_MAX: 2147483647
Time:     1508695069
Random 1: 705270938
Number y: 3
Random 2: 1758232243
Number z: 8

RAND_MAX: 2147483647
Time:     1508695074
Random 1: 1465504939
Number y: 6
Random 2: 733780153
Number z: 3

RAND_MAX: 2147483647
Time:     1508695080
Random 1: 1948289010
Number y: 9
Random 2: 1222424564
Number z: 5

rand-13

Alternatively, you can do what the macOS documentation says and switch to arc4random(). The link is to 'legacy' XCode 5 documentation, but the functionality hasn't changed recently.

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    int x = ((unsigned int)time(NULL));
    int r1 = arc4random_uniform(INT_MAX);
    int y  = ((double)r1/(double)(RAND_MAX)*10);
    int r2 = arc4random_uniform(INT_MAX);
    int z  = ((double)r2/(double)(RAND_MAX)*10);
    printf("INT_MAX:  %d\n", INT_MAX);
    printf("RAND_MAX: %d\n", RAND_MAX);
    printf("Time:     %d\n", x);
    printf("Random 1: %d\n", r1);
    printf("Number y: %d\n", y);
    printf("Random 2: %d\n", r2);
    printf("Number z: %d\n", z);
    return 0;
}

This uses arc4random_uniform() to generate a value in the range 0..INT_MAX, the same range as the other functions generated. There is no analogue to srand() with the arc4random() family of functions.

Sample runs:

INT_MAX:  2147483647
RAND_MAX: 2147483647
Time:     1508695894
Random 1: 938614006
Number y: 4
Random 2: 851262647
Number z: 3

INT_MAX:  2147483647
RAND_MAX: 2147483647
Time:     1508695900
Random 1: 1332829945
Number y: 6
Random 2: 386007903
Number z: 1

INT_MAX:  2147483647
RAND_MAX: 2147483647
Time:     1508695913
Random 1: 1953583540
Number y: 9
Random 2: 1273643162
Number z: 5

rand-83

Note that if you want a random integer between 0 and 10, then you should use arc4random_uniform(10) directly and not do the computations.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    int x = ((unsigned int)time(NULL));
    int y = arc4random_uniform(10);
    int z = arc4random_uniform(10);
    printf("Time:     %d\n", x);
    printf("Number y: %d\n", y);
    printf("Number z: %d\n", z);
    return 0;
}

Example outputs:

Time:     1508696162
Number y: 5
Number z: 3

Time:     1508696163
Number y: 7
Number z: 5

Time:     1508696165
Number y: 3
Number z: 8

One further advantage of arc4random() et al is shown when I managed to run the program 3 times in a single second. With the other generators, these would have produced the same results because of using the same seed.

$ rand-83;rand-83;rand-83
Time:     1508696213
Number y: 4
Number z: 0
Time:     1508696213
Number y: 0
Number z: 1
Time:     1508696213
Number y: 9
Number z: 6
$

$ rand-31;rand-31;rand-31
Time 1508696334
Random 1: 1319865409
Number y: 6
Random 2: 1619339200
Number z: 7
Time 1508696334
Random 1: 1319865409
Number y: 6
Random 2: 1619339200
Number z: 7
Time 1508696334
Random 1: 1319865409
Number y: 6
Random 2: 1619339200
Number z: 7
$

JFTR: Code compiled on a MacBook Pro running macOS High Sierra 10.13. I used GCC 7.2.0 (rather than XCode 9) to compile, but the system library — and it is the library that is critical here.




回答2:


You have just learned an important lesson:

Do not use time(NULL) to seed your random number generator.

Yes, I know "everyone does that". That doesn't make it correct. (In fact, "everyone" doesn't do that, although it is very common in example code. It is very uncommon -- and even in the uncommon cases incorrect -- in code which actually requires random numbers for production purposes.)

The fact is, "time in seconds since epoch" is a very unrandom number. It takes a full year for the first six bits to change; for almost a day, only the low-order 16 bits change, and for a second -- which is enough time to start thousands of processes -- it doesn't change at all. Since you are guaranteed to be able to get the same random sequence if you use the same seed, it should be obvious that you need to work a bit harder to ensure that you have a more random seed.

So what to do? Most operating systems these days have some mechanism to generate a truly random number (or a number which is as close to be random as the laws of physics will allow). On many Unix and Unix-like systems -- including Mac OS X and Linux -- you can get a very good unsigned 32-bit random number by reading four bytes from /dev/random. That's too expensive to do for every random number you will need, but it's excellent as a seed:

uint32_t seed = 0;
FILE* rf = fopen("/dev/random", "r");
if (rf != NULL) {
  if (fread(seed, sizeof seed, 1, rf) != 1) seed = 0;
  fclose(rf);
}
if (!seed) { /* Fallback to some other mechanism */ }
srand(seed);

Independent of seeding, rand is often not a very good random number generator, particularly on implementations where RAND_MAX is 32767. But no random-number generator, no matter how good it is, can compensate for the lack of a good seeding strategy.

(Some libraries will seed automatically, but there is a good reason why manual seeding is part of the interface: you may need to rerun the same random number sequence. For example, if you are using a PRNG to generate random test data and some run of your test program triggers a bug, you will want to rerun the program using the same random number sequence to verify that the bug has been fixed. One simple strategy is to use a command-line flag to supply an explicit random number seed, and ensure that the test harness always prints the random number seed which gave rise to the results.)




回答3:


after removing the (unneeded) clutter from your program, the following code:

  1. cleanly compiles
  2. eliminates unneeded local variables
  3. performs the desired functionality.
  4. clearly exposes what is actually happening with the time() and rand() functions.
  5. documents why each header file is included

and now the proposed code:

#include <stdio.h>  // printf()
#include <time.h>   // time()
#include <stdlib.h> // srand(), rand()

int main( void )
{
    srand((unsigned int)time(NULL));

    printf("Time %u\n\n", (unsigned int)time(NULL) );

    printf("first call to rand:  %d\n\n", rand());

    printf("second call to rand: %d\n\n", rand());

    return 0;
}

a typical run of the proposed code produces outputs similar to the following:

Time 1508783846

first call to rand:  1113266715

second call to rand: 810898123



回答4:


I actually managed to identify the problem and it is related to the use of Xcode 9.1 beta, which for some reason broke srand and rand(). After reverting back to Xcode 9.0.1, everything is working fine again.



来源:https://stackoverflow.com/questions/46877089/strange-behaviour-of-rand-in-xcode

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!