TVs. Consoles. Projectors and accessories. Technologies. Digital TV

Pseudorandom numbers. Generating random numbers in .NET Equiprobable random numbers

Programming languages ​​usually have functions that allow you to generate random numbers within a default range. In fact, it is not random numbers that are generated, but so-called pseudo-random numbers; they look random, but are calculated using a very specific formula. But for simplicity, below we will still call them random.

In the C programming language, you can get a random number using the rand() function, which is included in the standard library of the language. This function does not accept any parameters.

Exercise
Write a program that assigns the result of the rand() function to an integer variable. Display the value of the variable on the screen.

The rand() function returns an integer from 0 to the value assigned to the RAND_MAX constant. The value of RAND_MAX is system-specific and is defined in the stdlib.h header file. So, for example, it could be 32767 (a two-byte integer) or 2147483647 (a four-byte integer).

Exercise
Determine the value of RAND_MAX on your system. To do this, do not forget to include the stdlib.h header file in the source code file.

The code below prints 50 random numbers on the screen:

#include #include main() ( char i; for (i = 1 ; i<= 50 ; i++ ) { printf ("%15d" , rand () ) ; if (i % 5 == 0 ) printf ("\n") ; } }

The body of the loop moves to a new line after every five numbers displayed on the screen. To do this, use an expression that contains the remainder of dividing i by 5, the result is compared with 0. To prevent a transition to a new line after the first number, i is first assigned one, not zero (since 0 is divisible by 5 without a remainder) .

Exercise
Copy the code above. Run the program several times, noticing whether you get different results from run to run.

You should have noticed that every time you run the program the numbers remain the same. Even if you recompile the program, the result will not change. This effect is due to the fact that the initial (initializing) number, which is substituted into the formula for calculating the first and subsequent pseudo-random numbers, is always the same for each system. However, this seed can be changed using the srand() function, which is passed any integer as a parameter. It is clear that if you specify a specific argument for a function, for example, srand(1000) , then the numbers will also be the same from call to call of the program. Although not the same as they would have been without srand() . So the problem arises, how to make the argument for srand() also random? It turns out to be a vicious circle.

Exercise
Rework the program that prints 50 random numbers to first ask the user for any integer using scanf() and pass it to the srand() function.

The user of the program can set the initializing value himself. But most often this is not a complete way out of the situation. Therefore, the initializing value is tied to some process occurring in the operating system, for example, to a clock. Time (taking into account not only the time of day, but also the date) is never the same. This means that the value for srand() converted to an integer from system time will be different.

The current time can be found using the time() function, the prototype of which is described in the time.h file. By passing time() as a NULL parameter, we get an integer that can be passed to srand():
srand(time(NULL));

Exercise
Rework your program so that the initialization value depends on the system time.

Obtaining random integer numbers in specified ranges

The rand() function produces a random number between 0 and RAND_MAX. What should you do if you need to receive random numbers in other ranges, for example, from 100 to 999?

First, let's consider a simpler situation: get random numbers from 0 to 5. If you try to divide any integer by 5, you can get both 0 (when the number is divisible by 5 without a remainder) and 1, 2, 3, 4. For example , rand() returned the number 283. Applying the operation of finding the remainder of division by 5 to this number, we get 3. That is the expression rand() % 5 gives any number in the range ? It is logical to assume that you should find the remainder of division by 6. In this case, the following reasoning would be more literate: you need to find the remainder of division by the size of the range. In this case, it is equal to six values: 0, 1, 2, 3, 4, 5. To find the size of the range, you need to subtract the acceptable minimum from the permissible maximum and add one: max - min + 1. Be careful: if, for example, you need so that the maximum specified in the problem does not fall within the range, then there is no need to add one, or one must be subtracted from the maximum.

Exercise
Write a program that produces 50 random numbers from 0 to 99 inclusive.

So, we know the formula for obtaining the length of the range: max - min + 1. If you need to get a number from 6 to 10 inclusive, then the length of the range will be equal to 10 - 6 + 1 = 5. The expression rand()% 5 will give any number from 0 to 4 inclusive. But we need from 6 to 10. In this case, it is enough to add 6 to the resulting random remainder, i.e. minimum. In other words, you need to perform a shift. Valid for the example given:

  • if the remainder was 0, then adding 6, we get 6;
  • remainder 1, add 6, get 7;
  • remainder 4, add 6, get 10;
  • there cannot be more than 4 remainder.

In this case, the formula for obtaining a random number in a range looks like this:

Rand() % range_length + offset

where range_length is calculated as b - a + 1, offset is the value of a.

This formula also includes cases when it is necessary to obtain a random number from 0 to N, i.e. they are special cases of it.

Exercise
Display a series of random numbers belonging to the range from 100 to 299 inclusive.

You can just as easily get random negative numbers. Indeed, if the range is specified as [-35, -1], then its length will be equal to -1 - (-35) + 1 = 35, which is true; The expression for obtaining a random number will look like this:

rand() % 35 - 35

So, if the remainder of the division is 0, then we get -35, and if 34, then -1. The remaining remainders will give values ​​in the range from -35 to -1.

Exercise
Display a series of random numbers belonging to the range from -128 to 127 inclusive.

Getting Real Random Numbers

The situation with real numbers looks somewhat different. First, we cannot get the remainder of a division if the dividend or divisor is a fraction. Secondly, when calculating the length of a range, you cannot add one.

Let us explain the second reason. Let's say the range is specified as . It does not consist of a certain number of numbers (as in the case of integers), but of an indefinite (one might say infinite) number of values, because real numbers can be represented with varying degrees of precision. Later, when rounding, there will still be a chance to get the maximum limit of the range, so to calculate the length of the range, it is enough to subtract the minimum from the maximum.

If you divide the real-converted random number produced by the rand() function by the value of the constant RAND_MAX, you get a real random number between 0 and 1. Now, if you multiply this number by the length of the range, you get a number that lies in the range from 0 to range length value. Next, if you add to it the offset to the minimum limit, the number will safely fit into the required range. Thus, the formula for obtaining a random real number looks like this:

(float) rand() / RAND_MAX * (max - min) + min

Exercise
Fill the array with random numbers in the range from 0.51 to 1.00. Display the value of the array elements on the screen.

Equally probable random numbers

The rand() function generates any random number from 0 to RAND_MAX with equal probability. In other words, the number 100 has the same chance of appearing as the number 25876.

To prove this, it is enough to write a program that counts the number of occurrences of each value. If the sample (the number of “subjects”) is large enough, and the range (scatter of values) is small, then we should see that the percentage of occurrences of one or another value is approximately the same as that of others.

#include #include #define N 500 main () ( int i; int arr[ 5 ] = ( 0 ) ; srand (time (NULL) ) ; for (i= 0 ; i< N; i++ ) switch (rand () % 5 ) { case 0 : arr[ 0 ] ++; break ; case 1 : arr[ 1 ] ++; break ; case 2 : arr[ 2 ] ++; break ; case 3 : arr[ 3 ] ++; break ; case 4 : arr[ 4 ] ++; break ; } for (i= 0 ; i < 5 ; i++ ) printf ("%d - %.2f%%\n", i, ((float ) arr[ i] / N) * 100 ) ;

)

Greetings to everyone who stopped by. This short note will contain a few words about generating pseudorandom numbers in C/C++. In particular, about how to work with the simplest generation function - rand().

rand() function

Found in the C++ standard library (stdlib.h). Generates and returns a pseudorandom number in the range from 0 to RAND_MAX . This constant may vary depending on the compiler or processor architecture, but is generally the maximum value of the unsigned int data type. It does not accept parameters and never has.

In order for the generation to take place, you need to set the seed using an auxiliary function from the same library - srand() . It takes a number and sets it as the starting point for generating a random number. If the seed is not set, then every time the program starts, we will receive the same random numbers. The most obvious solution is to send the current system time there. This can be done using the time(NULL) function. This function is in the time.h library.

We've sorted out the theory, found all the functions for generating random numbers, now let's generate them.

An example of using the rand() random number generation function

#include #include #include using namespace std; int main() ( srand(time(NULL)); for(int i = 0; i< 10; i++) { cout << rand() << endl; } return 0; }

Rand generated 10 random numbers for us. You can verify that they are random by running the program again and again. But this is not enough, so we need to talk about range.

Set range boundaries for rand()

To generate a number in the range from A to B inclusive, you need to write this:

A + rand() % ((B + 1) - A);

Boundary values ​​can also be negative, which allows you to generate a negative random number, let's look at an example.

#include #include #include using namespace std; int main() ( srand(time(NULL)); int A = -2; int B = 8; for(int i = 0; i< 100; i++) { cout << A + rand() % ((B + 1) - A) << endl; } return 0; }

Tags: C random, C random numbers, random number generation, RNG, pseudo-random numbers, Monte Carlo method

Pseudorandom numbers

Generating pseudorandom numbers is a complex mathematical problem. This article does not set out to cover this topic. In what follows, the concept of “random number” will mean pseudo-random, unless otherwise specified.

You come across examples of the use of random numbers everywhere. Pseudorandom numbers are used in design and graphics, to generate levels in computer games and to simulate AI. Sets of random numbers are used in mathematical algorithms (see Monte Carlo methods).

Obviously, the problem of generating random numbers cannot be solved on a classical processor, since the operation of a computer is deterministic by definition. However, it is possible to generate very long sets of numbers such that their distribution has the same properties as sets of truly random numbers.

It is important that to solve a particular problem you need to choose the right generator, or at least know its properties. For example, when modeling a physical process, you can get completely different and often incorrect results, depending on the choice of random number generator.

Let's look at a standard generator.

#include #include #include int main() ( int i, r; srand(42); for (i = 0; i< 10; i++) { r = rand(); printf("%d\n", r); } _getch(); return 0; }

First, you need to initialize the random number generator (RNG, or RNG - random number generator), set the seed, on the basis of which the generation will take place in the future. It is important that for the same initial value the generator will return the same numbers.

Srand(42);

Assign variable r a random value

R = rand();

The value will be in the range from 0 to RAND_MAX.

In order to get a new set of numbers the next time you start, you need to initialize the generator with different values ​​each time. For example, you can use the system time:

Srand(time(NULL));

Srand(_getpid());

The getpid function of the process.h library returns the process ID (you can also use getpid, the non-POSIX version of the function).

Central Limit Theorem

It is very important to immediately remind or introduce the central limit theorem. Informal definition: the distribution of a sum of weakly dependent random variables tends to normal. Finger-shaped explanation: if you add several random variables, regardless of their distribution, the distribution of the sum will be normal. You can often see code like this

#include #include #include int main() ( int i, r, r1, r2, r3; srand(time(NULL)); r1 = rand(); r2 = rand(); r3 = rand(); r = (r1 + r2 + r3 ) / 3; printf("%d", r);

Generating random numbers on a given interval

First, we get a random number from zero to one:

Const float RAND_MAX_F = RAND_MAX; float get_rand() ( return rand() / RAND_MAX_F; )

To get a number in the interval from zero to N, multiply N by a random number from zero to one. To obtain a random number from M to N, we shift the resulting number by M.

Float get_rand_range(const float min, const float max) ( return get_rand() * (max - min) + min; )

To obtain an integer, we will take the remainder of division by the length of the interval. But the remainder of the division will return a number one less than our interval, so we increase it by one:

Int get_rand_range_int(const int min, const int max) ( return rand() % (max - min + 1) + min; )

An example of using random numbers to calculate an integral. Let us have some smooth function of one variable. Let's limit it to a square from a to b, and from 0 to some point, which is obviously larger than our function.

We will randomly throw points on our square. If they lie above the function (shown as green crosses in the figure), then we will assign them to the first group A, if below the function (red in the figure), then we will assign them to the second group B. The position of the points is random and distributed evenly (since the standard the generator gives a uniform distribution. This simple example, by the way, already shows how important it is to know the properties of the RNG). Then the ratio of red dots to the total number of dots will be equal to the ratio of the area under the graph to the total area. And the total area is the square (b-a) by q.

Src="/images/c_random_integral.png" alt=" Everything that randomly falls above our function is green, everything below is red.
The ratio of green to red will be equal to the ratio of the area above the graph to the area below the graph."> Всё, что случайно попадает выше нашей функции - зелёное, всё что ниже - красное. !}
The ratio of green to red will be equal to the ratio of the area above the graph to the area below the graph.

Let's apply our calculations - find the integral of the function x^2 on the interval from 0 to two in two ways.

#include #include #include #include #include const float RAND_MAX_F = RAND_MAX; float get_rand() ( return rand() / RAND_MAX_F; ) float get_rand_range(const float min, const float max) ( return get_rand() * (max - min) + min; ) #define ROUNDS 1000 float fun(float x) ( return x * x; ) float square_square(float a, float b, float q) ( float h = (b - a) / (float)ROUNDS; float sum = 0; for (; a< b; a += h) { sum += fun(a) * h; } return sum; } float rand_square(float a, float b, float q) { float res; float x, y; int i; int lower = 0; float ratio; float square; srand(time(NULL)); for (i = 0; i < ROUNDS; i++) { x = get_rand_range(a, b); y = get_rand_range(0, q); res = fun(x); if (res >y) ( lower++; ) ) ratio = (float)lower / (float)ROUNDS;

square = (b - a) * q * ratio;

return square; ) int main() ( float abs_ans = 2.66667f; float sr = rand_square(0, 2, 4); float ss = square_square(0, 2, 4); printf("Rounds = %d\n", ROUNDS); printf("Sa = %.5f\n", abs_ans); printf("Sr = %.5f\n", sr); printf("Ss = %.5f\n", ss); %.5f\n", fabs(sr - abs_ans)); printf("ds = %.5f\n", fabs(ss - abs_ans)); _getch(); return 0; )

To generate real random numbers, generators based on some random physical processes are used. For example, on thermal noise, on counting the number of fissions of a radioactive substance, on atmospheric noise, etc. The disadvantage of such generators is their low operating speed (the number of generated numbers per second); of course, such generators are usually a separate device.

Very often in programs there is a need to use random numbers - from filling an array to cryptography. To obtain a sequence of random numbers, the C# language has the Random class. This class provides two constructors:

  • Random()- initializes an instance of the Random class with an initial value that depends on the current time. As is known, time can be represented in ticks- 100 nanosecond pulses starting January 1, 0001. And the time value in ticks is a 64-bit integer, which will be used to initialize the random number generator instance.
  • Random(Int32)- initializes an instance of the Random class using the specified initial value. Initializing a random number generator in this way can be convenient when debugging a program, since in this case the same “random” numbers will be generated each time the program is launched.
The main method of this class is the Next() method, which allows you to get a random number and has a number of overloads:
  • Next() - returns a random non-negative integer in Int32 format.
  • Next( Int32)- returns a random non-negative integer that is less than the specified value.
  • Next( Int32 min, Int32 max)- returns a random integer in the specified range. In this case, the condition min must be met
And also methods
  • NextBytes( Byte)- fills the elements of the specified byte array with random numbers.
  • NextDouble() - returns a random floating point number, in the range )
    break ; // match found, element does not match
    }
    if (j == i)
    { // no match found
    a[i] = num; // save the element
    i++; // go to the next element
    }
    }
    for (int i = 0; i< 100; i++)
    {

    if (i % 10 == 9)
    Console.WriteLine();
    }
    Console .ReadKey();
    }
    }
    }

    However, the closer to the end of the array, the more generations must be performed to obtain a non-repeating value.
    The following example displays the number of calls to the Next() method to obtain each element, as well as the total number of random numbers generated to fill a 100-element array with non-repeating values.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53

    using System;
    namespace MyProgram
    {
    class Program
    {
    static void Main(string args)
    {
    Random rnd = new Random();
    int a = new int ; // array of elements
    int count = new int ; // array of number of generations
    a = rnd.Next(0, 101);
    int c = 0; // counter of the number of generations
    count = 1; // a is generated only once
    for (int i = 1; i< 100;)
    {
    int num = rnd.Next(0, 101);
    c++; // generated the element one more time
    int j;
    for (j = 0; j< i; j++)
    {
    if (num == a[j])
    break ;
    }
    if (j == i)
    {
    a[i] = num; i++;
    count[i] = c; c = 0; // save the number of generations
    }
    }
    // Printing element values
    Console .WriteLine( "Element Values");
    for (int i = 0; i< 100; i++)
    {
    Console .Write("(0,4) " , a[i]);
    if (i % 10 == 9)
    Console.WriteLine();
    }
    Console.WriteLine();
    // Display the number of generations
    Console .WriteLine( "Number of element generation");
    int sum = 0;
    for (int i = 0; i< 100; i++)
    {
    sum += count[i];
    Console .Write("(0,4) " , count[i]);
    if (i % 10 == 9)
    Console.WriteLine();
    }
    Console .WriteLine( "Total number of generations - (0)", sum);
    Console .ReadKey();
    }
    }
    }

    void Main(string args)
    {
    Random rnd = new Random();
    int a = new int ;
    for (int i = 0; i< 100; i++)
    a[i] = i;
    for (int i = 0; i< 50; i++)
    {
    int i1 = rnd.Next(0, 100); // first index
    int i2 = rnd.Next(0, 100); // second index
    // exchange of values ​​of elements with indices i1 and i2
    int temp = a;
    a = a;
    a = temp;
    }
    Console .WriteLine( "Element Values");
    for (int i = 0; i< 100; i++)
    {
    Console .Write("(0,4) " , a[i]);
    if (i % 10 == 9)
    Console.WriteLine();
    }
    Console .ReadKey();
    }
    }
    }

    Shuffling values ​​is more effective if the range of values ​​matches (or is close to) the number of values, since in this case the number of random element generation is significantly reduced.

    Translation of the article Random numbers by Jon Skeete, widely known in narrow circles. I stopped at this article because at one time I myself encountered the problem described in it.

    Browsing topics by .NET And C# on the StackOverflow website, you can see countless questions mentioning the word “random”, which, in fact, raise the same eternal and “indestructible” question: why does the System.Random random number generator “not work” and how to “fix it” " This article is devoted to consideration of this problem and ways to solve it.

    Formulation of the problem

    On StackOverflow, in newsgroups and mailing lists, all questions on the topic “random” sound something like this:
    I'm using Random.Next to generate multiple random numbers, but the method returns the same number when called multiple times. The number changes each time the application is launched, but within one execution of the program it is constant.

    An example code is something like this:
    // Bad code! Do not use! for (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
    So what's wrong here?

    Explanation

    The Random class is not a true random number generator, it contains a generator pseudo random numbers. Each instance of the Random class contains some internal state, and when the Next (or NextDouble, or NextBytes) method is called, the method uses that state to return a number that will appear random. The internal state is then changed so that the next time Next is called, it will return a different seemingly random number than the one previously returned.

    All the “internals” of the Random class completely deterministic. This means that if you take several instances of the Random class with the same initial state, which is specified through the constructor parameter seed, and for each instance call certain methods in the same order and with the same parameters, then in the end you will get the same results.

    So what's wrong with the above code? The bad thing is that we use a new instance of the Random class inside each iteration of the loop. The Random constructor, which takes no parameters, takes the current date and time as its seed. The iterations in the loop will “scroll” so quickly that the system time “will not have time to change” upon their completion; thus, all instances of Random will receive the same value as their initial state and will therefore return the same pseudo-random number.

    How to fix it?

    There are many solutions to the problem, each with its own pros and cons. We'll look at a few of them.
    Using a Cryptographic Random Number Generator
    .NET contains an abstract class RandomNumberGenerator from which all implementations of cryptographic random number generators (hereinafter referred to as cryptoRNGs) must inherit. .NET also contains one of these implementations - meet the RNGCryptoServiceProvider class. The idea of ​​a crypto-RNG is that even if it is still a pseudo-random number generator, it provides a fairly strong unpredictability of results. RNGCryptoServiceProvider uses several sources of entropy, which are essentially "noise" in your computer, and the sequence of numbers it generates is very difficult to predict. Moreover, "in-computer" noise can be used not only as an initial state, but also between calls to subsequent random numbers; thus, even knowing the current state of the class, it will not be enough to calculate both the next numbers that will be generated in the future, and those that were generated previously. In fact, the exact behavior is implementation dependent. In addition, Windows can use specialized hardware that is a source of "true randomness" (for example, a radioactive isotope decay sensor) to generate even more secure and reliable random numbers.

    Let's compare this with the previously discussed Random class. Let's say you called Random.Next(100) ten times and saved the results. If you have enough computing power, you can, based solely on these results, calculate the initial state (seed) with which the Random instance was created, predict the next results of calling Random.Next(100), and even calculate the results of previous method calls. This behavior is extremely unacceptable if you are using random numbers for security, financial purposes, etc. Crypto RNGs operate significantly slower than the Random class, but they generate a sequence of numbers, each of which is more independent and unpredictable from the values ​​of the others.

    In most cases, poor performance isn't a deal breaker - a bad API is. RandomNumberGenerator is designed to generate sequences of bytes - that's all. Compare this with the methods of the Random class, where it is possible to obtain a random integer, fractional number, and also a set of bytes. Another useful property is the ability to obtain a random number in a specified range. Compare these possibilities with the array of random bytes that RandomNumberGenerator produces. You can correct the situation by creating your own wrapper (wrapper) around RandomNumberGenerator, which will convert random bytes into a “convenient” result, but this solution is non-trivial.

    However, in most cases, the "weakness" of the Random class is fine if you can solve the problem described at the beginning of the article. Let's see what we can do here.

    Use one instance of the Random class for multiple calls
    Here it is, the root of the solution to the problem is to use only one instance of Random when creating many random numbers using Random.Next. And it's very simple - see how you can change the above code:
    // This code will be better Random rng = new Random(); for (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
    Now each iteration will have different numbers... but that's not all. What happens if we call this block of code twice in a row? That's right, we'll create two Random instances with the same seed and get two identical sets of random numbers. The numbers in each set will be different, but these sets will be equal among themselves.

    There are two ways to solve the problem. Firstly, we can use not an instance, but a static field containing an instance of Random, and then the above piece of code will create only one instance, and will use it, calling it as many times as necessary. Secondly, we can completely remove the creation of a Random instance from there, moving it “higher”, ideally to the very “top” of the program, where a single Random instance will be created, after which it will be transmitted to all places where random numbers are needed. This is a great idea, nicely expressed by dependencies, but it will work as long as we only use one thread.

    Thread safety

    The Random class is not thread safe. Considering how much we like to create a single instance and use it throughout a program for the entire duration of its execution (singleton, hello!), the lack of thread safety becomes a real pain in the ass. After all, if we use one instance simultaneously in several threads, then there is a possibility that its internal state will be reset, and if this happens, then from that moment on the instance will become useless.

    Again, there are two ways to solve the problem. The first path still involves using a single instance, but this time using resource locking via a monitor. To do this, you need to create a wrapper around Random that will wrap calls to its methods in a lock statement, ensuring exclusive access to the instance for the caller. This path is bad because it reduces performance in thread-intensive scenarios.

    Another way, which I will describe below, is to use one instance per thread. The only thing we need to make sure is that we use different seeds when creating instances, so we can't use default constructors. Otherwise, this path is relatively straightforward.

    Secure provider

    Fortunately, the new generic class ThreadLocal , introduced in .NET 4, makes it very easy to write providers that provide one instance per thread. You just need to pass a delegate to the ThreadLocal constructor, which will refer to getting the value of our instance itself. In this case, I decided to use a single seed value, initializing it using Environment.TickCount (which is how the parameterless Random constructor works). Next, the resulting number of ticks is incremented every time we need to get a new Random instance for a separate thread.

    The class presented below is completely static and contains only one public (open) method GetThreadRandom. This method is made a method rather than a property, mainly for convenience: this will ensure that all classes that need an instance of Random will depend on Func (a delegate pointing to a method that takes no parameters and returns a value of type Random), and not from the Random class itself. If a type is intended to run on a single thread, it can call a delegate to obtain a single instance of Random and then use it throughout; if the type needs to work in multi-threaded scenarios, it can call the delegate every time it needs a random number generator. The class below will create as many instances of the Random class as there are threads, and each instance will start from a different initial value. If we need to use the random number provider as a dependency in other types, we can do this: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom) . Well, here is the code itself:
    using System; using System.Threading; public static class RandomProvider ( private static int seed = Environment.TickCount; private static ThreadLocal randomWrapper = new ThreadLocal (() => new Random(Interlocked.Increment(ref seed)));
    public static Random GetThreadRandom() ( return randomWrapper.Value; ) )

    Simple enough, right? This is because all the code is aimed at producing the correct instance of Random. Once an instance is created and returned, it doesn’t matter what you do with it next: all further issuances of instances are completely independent of the current one. Of course, the client code has a loophole for malicious misuse: it can take one instance of Random and pass it to other threads instead of calling our RandomProvider on those other threads.

    Interface design problems

    It would be very nice if RNG providers in the framework had separate “sources of randomness”. In this case, we could have a single, simple and convenient API that would be supported by both the insecure-but-fast implementation and the secure-but-slow one. Well, there's no harm in dreaming. Perhaps similar functionality will appear in future versions of the .NET Framework. Perhaps someone not from Microsoft will offer their own implementation of the adapter. (Unfortunately, I won't be that person...implementing something like this correctly is surprisingly complex.) You can also create your own class by deriving from Random and overriding the Sample and NextBytes methods, but it's not clear exactly how they should work, or even your own Implementing Sample can be much more complex than it seems. Maybe next time…



Related publications