cmacossrand

srand/rand slowly changing starting value


I have written a simple BASIC interpreter that I'm running on Clang/macOS. To implement RND(), desiring to use as standard C as possible, I used srand/rand. Near the start of the program (in retrobasic.c) I call srand (yes, once) with either null or a user-passed value:

if (random_seed > -1)
  srand(random_seed);
else
  srand(time(NULL));

There is only one place that a number is produced:

result.number = ((double)rand() / (double)RAND_MAX); 

The code that's calling this is INT(RND()*25)+1 and I noticed it returns the same starting number every run, but it changes every hour or so. So last night it was always 25, this morning was 2, and now it's 3. I put a printf("%d", rand()); right after the srand. Sure enough, that gets me:

200829549  
200964005  
201098461  
201232917  
201703513

That seems to be pseudo-seconds in the 3rd/4th digits, which explains the behaviour I'm seeing. This added printf "fixes" it by pumping the sequence and then any subsequent calls to rand are fine. I would really like to understand/solve this the right way.

Clang complains about the srand(time(NULL)), saying it takes an unsigned int and wants me to cast the time_t to silence the warning. time_t is implementation dependant, but I suspect it is really a unsigned long? Perhaps this is an issue with a 32/64 bit conversion going into srand? I found a few tantalizing posts, like this one, but no ultimate conclusion.

This is not about bad random sequence, the sequence is fine (for my needs). There are threads that are about bad starting numbers, but invariably they end up being improper srand use. Does anyone have a suggestion here?


Solution

  • The effect you describe suggests that the highest-order bits of the output of your system's rand() are insensitive to the lowest-order bits of its internal state (which you initialize via srand()). From your "every hour or so", I speculate that you may need a difference at bit 12 or higher, corresponding to 4096 s or about 68 minutes, to overcome that effect.

    This added printf "fixes" it by pumping the sequence and then any subsequent calls to rand are fine.

    That would be consistent. Although the first numbers pulled from the generator on runs performed close together may have similar values, the effects on the PRNG's internal state will be different enough that subsequent results then diverge.

    A simple solution, then, might be simply to discard a couple of initial results from rand(), immediately after the srand() call:

    if (random_seed > -1) {
      srand(random_seed);
    } else {
      srand(time(NULL));
    }
    rand();
    rand();
    

    Alternatively, any approach that causes your seeds to differ more widely is likely to help even with the initial numbers. @SteveSummit's idea of combining the PID with the output of time(NULL) could be that, especially if you shift or rotate the PID as he suggested.* For example, something like

        unsigned int my_pid = getpid();
        unsigned int random_seed = ((unsigned int) time(NULL)) ^ (my_pid << 8);
    
        // ...
    

    You could go further to get initial seeds that vary more randomly, but my reading of the question suggests that either of the above would be adequate for your needs. And they are not mutually exclusive, of course.


    *But note that getpid() and PIDs in general are not native to C. They are POSIX features, so they may not themselves be suitable for your use if you want to rely only on the language spec.