4

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.

homersimpson
  • 4,124
  • 4
  • 29
  • 39
  • 2
    Try using the process id as well as time. Or use a time function with more resolution. http://linux.die.net/man/3/clock_gettime – ooga Feb 06 '14 at 04:43
  • 4
    Pick a genuine random seed, not the time. – Kerrek SB Feb 06 '14 at 04:48
  • @KerrekSB: What is a "genuine" random seed? – Keith Thompson Feb 06 '14 at 08:18
  • @KeithThompson: Something like `/dev/random` should do the trick for most cases (especially the OP's) and can provide the required four bytes of random seed; just something that isn't obviously derived from simple environment paramenters... – Kerrek SB Feb 06 '14 at 16:01
  • If you are on x86, you could use rdtsc to get some "random" value from the CPU clock. – wildplasser Feb 06 '14 at 21:31

5 Answers5

5

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
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Thanks for the help - this was a really descriptive answer! I bookmarked this page so I can come back to this later. I ended up seeding random with the PID in @loreb's answer to solve the problem in my program, since two programs that run at the same time won't start with the same seed. – homersimpson Feb 10 '14 at 00:23
3

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))))
loreb
  • 1,327
  • 1
  • 7
  • 6
2

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.

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • `urandom` w/o the `while` loop would be enough for his purpose :-P – nodakai Feb 06 '14 at 05:20
  • @nodakai: `urandom` is not always available, and `random` is not always non-blocking. This is the price you pay for portability. – Dietrich Epp Feb 06 '14 at 05:58
  • How many have either `/dev/random` or `/dev/urandom` but not both? – Keith Thompson Feb 06 '14 at 08:19
  • Hmm, I added this function in my program, and got this compilation error: `error: expected identifier or '(' before '/' token`. Any idea why this might be? – homersimpson Feb 10 '14 at 00:29
  • @homersimpson: Your compiler does not understand `//` as a comment. You probably are passing `-std=c90` or `-ansi` to the compiler. Try using `-std=gnu90`, `-std=c99`, or any newer version. – Dietrich Epp Feb 10 '14 at 01:05
  • @DietrichEpp Ah, yes. I don't know why I didn't see that. I use `-ansi` and `-pedantic` as flags to the compiler, so of course that would throw it off. It does work though, thank you! – homersimpson Feb 10 '14 at 18:26
1
  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
nodakai
  • 7,773
  • 3
  • 30
  • 60
  • What is the basis for recommending that you throw away numbers from a PRNG? – Dietrich Epp Feb 06 '14 at 05:59
  • What do you mean by "diverge"? I think the 10,000 wasted rounds just make the CPU hot. Linear congruential generators have monoid structure, after all, so one round with different constants has the same effect. But the word "diverge" seems to imply some sort of correlation between generators. I'd like to see what kind of correlation you can observe. – Dietrich Epp Feb 06 '14 at 07:58
  • Instead of `|`, recommend `^` in `((unsigned)getpid() << 16) ^ (0xFFFFu & (unsigned)time(NULL));`. Using the `|` will bias the `seed` to having many bits set. – chux - Reinstate Monica Feb 06 '14 at 15:27
  • @nodakai Throwing away a _fixed_ count of numbers does not forget the initial seed. 2 runs of the same program, with the same seed, throwing away 10,000 numbers does not create a different sequence in the 10,001 to 10,020 numbers. I am not seeing how throwing away a fixed number of `rand()` helps OP and am confident it does not differentiate 2 random number sequences. – chux - Reinstate Monica Feb 06 '14 at 16:56
  • I cleaned up my comments. I threw away the first 10,000 numbers in hope that I can get rid of the weak point of using `getpid()` whose return values are surely distinct but can be sequential for multiple processes started within a second. – nodakai Feb 06 '14 at 18:10
0

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() );
Will
  • 33
  • 1
  • 8
  • 1
    This is a really bad way of doing things. When I tested this on my system, `clock()` always returned 0, so this would seed the RNG with 0 every time. Naturally one tends to seed the RNG at program startup where the result of `clock()` is low (or possibly zero). – Dietrich Epp Feb 06 '14 at 08:01
  • @Dietrich Didn't think of that, perhaps it would have been better for me to suggest time(NULL) + clock() instead, though as you mentioned if called in the beginning of a program it has a high likelihood of returning 0 which would not solve the OP's original problem of guaranteeing a unique random number. – Will Feb 06 '14 at 08:38