Seeding a pseudo-random number generator in C#
Asked Answered
R

3

16

I need a seed for an instance of C#'s Random class, and I read that most people use the current time's ticks counter for this. But that is a 64-bit value and the seed needs to be a 32-bit value. Now I thought that the GetHashCode() method, which returns an int, should provide a reasonably distributed value for its object and this may be used to avoid using only the lower 32-bits of the tick count. However, I couldn't find anything about the GetHashCode() of the Int64 datatype.

So, I know that it will not matter much, but will the following work as good as I think (I can't trial-and-error randomness), or maybe it works the same as using (int)DateTime.Now.Ticks as the seed? Or maybe it even works worse? Who can shed some light on this.

int seed = unchecked(DateTime.Now.Ticks.GetHashCode());
Random r = new Random(seed);

Edit: Why I need a seed and don't just let the Random() constructor do the work? I need to send the seed to other clients which use the same seed for the same random sequence.

Ratiocinate answered 30/10, 2010 at 22:31 Comment(2)
You forgot to write your really good argument for needing a seed.Semipro
Well, I added that really good argument now. :)Ratiocinate
H
35

new Random() already uses the current time. It is equivalent to new Random(Environment.TickCount).

But this is an implementation detail and might change in future versions of .net

I'd recommend using new Random() and only provide a fixed seed if you want to get a reproducible sequence of pseudo random values.

Since you need a known seed just use Environment.TickCount just like MS does. And then transmit it to the other program instances as seed.

If you create multiple instances of Random in a short interval (could be 16ms) they can be seeded to the same value, and thus create the same pseudo-random sequence. But that's most likely not a problem here. This common pitfall is caused by windows updating the current time(DateTime.Now/.UtcNow) and the TickCount(Environment.TickCount) only every few milliseconds. The exact interval depends on the version of windows and on what other programs are running. Typical intervals where they don't change are 16ms or 1ms.

Haught answered 30/10, 2010 at 22:33 Comment(14)
+1 for mentioning that the only time to really provide your own seed value is when you want to reproduce a specific sequence of values. Think along the lines of the Windows Solitaire "game number" option.Cudweed
I edited my question. I know about the new Random() constructor, but I need the seed.Ratiocinate
@AndrewBarber Or if you iterate the method/block outside the generation of the Random, need unique seeds and want to avoid the risk of collisions due to insufficient time resolution, just as CodesInChaos writes.Retire
@Retire I'm fairly sure that's actually not what CodeInChaos is saying. I quote, "only provide a fixed seed if you want to get a reproducible sequence of...values". You shouldn't be creating so many Random instances.Cudweed
@AndrewBarber True, you should not keep the instantiation of a Random within a loop. But you could have a method that initiates an internal Random object in order to perform it's task, and this method might not have been explicitly designed to be used in a loop, but might in some circumstances very well used that way by the developers.Retire
@Retire you shouldn't try to fix a bad design by piling another bad design on top. If what you describe can happen, the method declaring the Random (or the whole object containing the method) probably smells all around.Cudweed
@AndrewBarber Well, I see it this way: The initiation of a Random is designed to be seeded differently each time, it's implemented by using the system clock, and do to insufficient resolution this is prone to collisions if called to fast. If you think that this vulnerability poses a risk, you can choose to employ external infrastructure to handle the management of the implementation symptoms, or you can do it as close to the source as possible. If I have a choice, I'd rather contain the weakness close to the source, instead of building external infrastructure around it. If possible.Retire
@Retire yes. Contain it close to the source... don't expose a possibility of constructing multiple Randoms in quick succession at all, no matter what calls you method, however often. But all this begs the question of what you would even pass in to the constructor in the first place... not the time; there's no Random you can use... see my point? You've trapped yourself in a circle. Only solution: prevent multiple Randoms like this all.Cudweed
@AndrewBarber But if it worked as intended, I would not have to care about investigating the system for the risk that some initiations of a Random might be called to fast in some possible chain of events. That's a significant overhead that's worth avoiding if you can. I'm not saying that you always can, or that it always will be worth it. One simple example could be this: GenerateAuthToken(username), where we can incorporate the username in the seed to easily make sure that it's highly unlikely that two different users will get the same token, even if called subsequently.Retire
@Retire 1) you should not be creating individual Random instances for those. 2) you should never use Random for anything needing to be in any way secure.Cudweed
@AndrewBarber Let's try to round of this discussion, but yes, in this isolated case I should for example use a static Random in the class. Or I might even keep a specific class for keeping a static Random. But the interesting feature of the example is that the randomness is most important as a projection together with other input values, that I implied authentication was a bad choice from my side. But yes, for most cases I think you're more right and that I might have overestimated the cases where seeding the Random manually is a valuable option. But is not never except reproduction.Retire
@Retire I was thinking the same thing ;) I do think you have a grasp on this, but I just think there's some tiny technicality or something that's dodging around between us! Poor CodeInChaos is gonna wonder why his answer blew up! ;)Cudweed
@AndrewBarber This is a pretty old answer, and I no longer fully agree with it. In my opinion even one instance of Random is too much. It can't be seeded properly and it generates biased output. Just throw it away and use a proper PRNG. If I really had to use Random, I'd add a thread-safe counter initialized to a random value as seed for thread-local instances of Random.Haught
@code yeah, it's really only worthwhile for trivial stuff, and that's otherwise a decent solution.Cudweed
C
31

If you need to seed it with something other than the current time (in which case you can use the default constructor), you can use this:

Random random = new Random(Guid.NewGuid().GetHashCode());
Chiffonier answered 30/10, 2010 at 22:38 Comment(6)
And why would that be a better value than DateTime.Now.Ticks.GetHashCode()?Ratiocinate
It's better because it doesn't suffer from the problem that DateTime.Now only changes every few milliseconds. With this method it's very unlikely that two instances of random get the same seed even when initialized in quick succession.Haught
And you're certain that there are no hash-collisions?Knightly
@Haught Exactly what oɔɯǝɹ says - there probably would be less if you just used the GUIDs. https://mcmap.net/q/423646/-guid-amp-gethashcode-uniquenessSparrowgrass
@oɔɯǝɹ Unfortunately hash collisions will be common. A 32 bit seed is simply too small to ensure good seeding.Haught
That approach will randomly blow up any time gethashcode happens to return int.minvalue due to random doing an abs on the seed and abs blowing up on int.minvalue. You need to do this instead... Random random = new Random(Guid.NewGuid().GetHashCode() & int.MaxValue); To ensure you're only sending in positive integers.Lollop
E
0

I had a similar question , to select a random set of questions from a larger list of questions. But when I use the time as the seed it gives the same random number .

So here is my solution.

    int TOTALQ = 7;
    int NOOFQ = 5;

    int[] selectedQuestion = new int[TOTALQ];

    int[] askQuestion = new int[NOOFQ];

    /*   Genarae a random number 1 to TOTALQ
     *   - if that number in selectedQuestion array is not o
     *   -     Fill askQuestion array with that number
     *   -     remove that number from selectedQuestion
     *   - if not re-do that - - while - array is not full.    
     */

    for (int i = 0; i < TOTALQ; i++)  // fill the array
        selectedQuestion[i] = 1;

    int question = 0;

    int seed = 1;

    while (question < NOOFQ)
    {       
        DateTime now1 = new DateTime();
        now1 = DateTime.Now;    
        Random rand = new Random(seed+now1.Millisecond);
         int RandomQuestion = rand.Next(1, TOTALQ);

         Response.Write("<br/> seed  " + seed + " Random number " + RandomQuestion );



        if (selectedQuestion[RandomQuestion] != 0)      
        {
            selectedQuestion[RandomQuestion] = 0;  // set that q =0 so not to select           
            askQuestion[question] = selectedQuestion[RandomQuestion];
            Response.Write(".  Question no " + question + " will be question " + RandomQuestion + " from list " );
            question++;
        }

        seed++;         

    }
Edelsten answered 19/9, 2014 at 8:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.