C - How to ENSURE different random number generation in C when program is executed within the same second?

StackOverflow https://stackoverflow.com/questions/21594140

  •  07-10-2022
  •  | 
  •  

Question

For an assignment, I am supposed to ensure that, even when I execute a program within the same second, I should return different numbers. However, I've read other posts and can't quite figure out how to do so specifically within the same second. If I run the code:

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

    for(count = 0; count < 10; count++)
    {
        printf("%d\n", rand()%20 + 1);
    }

    return 0;
}

I end up with the same result when executed within the same second. Does anyone know how to mix the results within the same second? I'm operating in a Unix environment if that makes a difference. Thanks.

Was it helpful?

Solution

To prevent 2 programs from generating the same pseudo-random numbers from rand(), they must, at a minimum effectively use different seeds for srand().

The source for the seeds for the 2 runs of the program could be derived from either

1) the same source - with a mechanism for unique generation.
2) a truly random source and the chance of same seed generation tolerable low.

1A With #1, time() is often used, but by definition, the programs start in the same second, so simplistic use of this fails.

1B Attempting to create a file that both program access to write something like "I started with this seed - 12345, so if you generate that seed try again".

1C Another approach is to use a finer time as hinted by (@Will) - That's better but the finer resolution may not be enough.

2A Some platforms provide a truly random number via a system function call, but since it depends on various esoteric system events , it is slow as compared to rand(), but useful for seeding srand(). Not all systems provide this.
[Edit] Did not see the Unix tag till later.
/dev/random & /dev/urandom (@Dietrich Epp) provide this feature.

2B One can use variant human reaction time like

printf("Press enter\n");
unsigned u = 0;
while (!keyboard_hit()) u++;
srand(u);

Both: Combining (via exclusive-or ^) various sources like pid(), time(), reaction_time(), etc. helps. (See @nodakai)


Even with employing various mechanism to prevent a different seeding of a random number generator, 2 different runs still could generate the same sequence: about 1 in 20^10 (10,240,000,000,000) times per OP's code. After all these are random numbers, coming up with the same sequence could happen.

To absolutely prevent duplication, the 2 program must communicate - at least in one direction. Maybe which ever program was first to write to a common file, the sequence generated, the next could inspect and insure the sequence it makes is different.

// pseudo code
n = 1;
repeat {
  srand(time()^n^pid());
  n++;
  generate_random_number_sequence();
  attempt exclusive r/w access to shared file.
  if (file opened) {
    read file;
    if (different sequence) {
      write new sequence and fclose()
      if (no I/O errors) {
        we are done - exit
      }
    }
    fclose()
  }
  maybe sleep for a fraction of a second
  maybe quit if repeated too often
}

OTHER TIPS

First of all, read chux's answer carefully.

Done that? good. If it's ok to assume that the programs will run on the same computer you can just use the PID, and that will guarantee that no two programs running at the same time started with the same seed.

The downside of this "guarantee" is that you'll typically end up using only a subset of available seeds (srand() takes an uint, which is usually 32 bits wide, while a pid is almost always smaller: eg on my system it's limited to 32k); you can work around it by setting the low bits to the pid and the high bits to something else, eg

srand(getpid() + (time(0) << (CHAR_BIT * sizeof(pid_t))))

Use /dev/random or /dev/urandom to get your seeds. The kernel will ensure that these are generated appropriately, usually by mixing physical entropy with a PRNG.

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
// Seed the random number generator.
void seed_rng(void)
{
    int fp = open("/dev/random", O_RDONLY);
    if (fp == -1) abort();
    unsigned seed;
    unsigned pos = 0;
    while (pos < sizeof(seed)) {
        int amt = read(fp, (char *) &seed + pos, sizeof(seed) - pos);
        if (amt <= 0) abort();
        pos += amt;
    }
    srand(seed);
    close(fp);
}

This code is a bit clumsy, because /dev/random can block. But it is a great way to seed your RNGs.

These requirements are a bit weird, because it's difficult to imagine a situation where using rand() is okay but seeding with the timestamp is not okay. The standard RNG is usually mediocre but simple and fast, and if you wanted something more sophisticated, you'd probably want to ditch rand() first.

  1. You can combine PID (process ID) with the current time to get a (hopefully) unique seed. If you are on Linux, try cat /proc/sys/kernel/pid_maxto get its max value.

    #include <sys/types.h>
    #include <unistd.h>
    
    unsigned seed = ((unsigned)getpid() << 16) | (0xFFFFu & (unsigned)time(NULL));
    

    Be careful not to simply add getpid() to time(), because getpid() tends to return sequential values for processes started sequentially.

  2. (This is not a direct solution for the OP's problem. Just a recommendation for general casese.) There are better alternatives of rand such as random/srandom with BSD origin.

  3. (This is not a direct solution for the OP's problem. Just a recommendation for general casese.) I recommend you to throw away first, say, 10,000 numbers generated by your PRNG.

    srand(time(NULL)); /* or whatever initialization */
    for(count = 0; count < 10000; count++) rand();
    
    for(count = 0; count < 10; count++)
    {
        printf("%d\n", rand()%20 + 1); /* or whatever random function */
    }
    

Edit

I could design an (artificial, I have to admit) test to see how throwing away some initial rounds can be something.

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

/* http://en.wikipedia.org/wiki/Linear_congruential_generator */
int doTest(int N) {
    int i;
    unsigned init, r;

    for (init = 1; init <= 10; ++init) {
        r = init;
        for (i = 0; i < N; ++i)
            r = 22695477 * r + 1;
        printf("%2d -> ...%3d times... -> %6u\n", init, N, (r>>16) & ((1<<15)-1));
    }
    printf("\n");
}

int main(void) {
    doTest(1);
    doTest(2);
    doTest(3);
    doTest(10000);
    return 0;
}

The parameters r = 22695477 * r + 1 and (r>>16) & ((1<<15)-1) are what I think are equivalent to Borland C/C++ rand implementation (see wikipedia.)

For the initial three rounds, the resulting PRN's still maintain as strong correlation as the initial values.

 1 -> ...  1 times... ->    346
 2 -> ...  1 times... ->    692
 3 -> ...  1 times... ->   1038
 4 -> ...  1 times... ->   1385
 5 -> ...  1 times... ->   1731
 6 -> ...  1 times... ->   2077
 7 -> ...  1 times... ->   2424
 8 -> ...  1 times... ->   2770
 9 -> ...  1 times... ->   3116
10 -> ...  1 times... ->   3463

 1 -> ...  2 times... ->    130
 2 -> ...  2 times... ->  32682
 3 -> ...  2 times... ->  32467
 4 -> ...  2 times... ->  32251
 5 -> ...  2 times... ->  32036
 6 -> ...  2 times... ->  31820
 7 -> ...  2 times... ->  31604
 8 -> ...  2 times... ->  31389
 9 -> ...  2 times... ->  31173
10 -> ...  2 times... ->  30957

 1 -> ...  3 times... ->  10982
 2 -> ...  3 times... ->  21834
 3 -> ...  3 times... ->  32686
 4 -> ...  3 times... ->  10770
 5 -> ...  3 times... ->  21622
 6 -> ...  3 times... ->  32473
 7 -> ...  3 times... ->  10557
 8 -> ...  3 times... ->  21409
 9 -> ...  3 times... ->  32261
10 -> ...  3 times... ->  10345

 1 -> ...10000 times... ->  13125
 2 -> ...10000 times... ->   5687
 3 -> ...10000 times... ->  31016
 4 -> ...10000 times... ->  23578
 5 -> ...10000 times... ->  16140
 6 -> ...10000 times... ->   8702
 7 -> ...10000 times... ->   1264
 8 -> ...10000 times... ->  26594
 9 -> ...10000 times... ->  19156
10 -> ...10000 times... ->  11718

You could use the clock() function which (According to http://www.cplusplus.com/reference/ctime/clock/) returns the number of clock ticks since the process started. This should usually vary between calls to the same function, so even if a program is called at the same time in seconds, there will be a slight difference in time which will result in different numbers being returned by your RNG.

srand( time(NULL) * clock() );
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top