Almost all programming languages provide the "generate a random number" method, that is, calling this method will generate a number, which we do not know beforehand. For example, write the following code in. Net:
Random rand = newRandom(); Console.WriteLine(rand.Next());
The result is as follows:
The Next () method is used to return a random number. The execution results of the same Code may be different from those of mine, and the results of multiple running operations may be different. This is a random number.
I. traps
Something that looks very simple. There are traps in use. I wrote the following code to generate 100 random numbers:
for(int i=0;i<100;i++)
{
Random rand = new Random();
Console.WriteLine(rand.Next());
}
It's strange that there are many consecutive "random numbers" generated. What is this "random number. Someone instructs you to put "new Random ()" out of the for Loop:
Random rand = newRandom();
for(int i=0;i<100;i++)
{
Console.WriteLine(rand.Next());
}
Running result:
Yes!
2. Why?
This starts with the principle of "random number" Generation in the computer. As we know, the computer is very strict. The results produced under the specified input conditions are unique and will not be different in each execution. So how can we use software to generate seemingly uncertain random numbers?
There are many algorithms used to generate random numbers. The simplest and most commonly used method is the linear same-remainder method: the number of n + 1 = (number of n * 29 + 37) % 1000, here, % is the "evaluate the remainder" operator. Many people like me have a headache when they see formulas. I will explain it in code. MyRand is a class for custom random number generation:
class MyRand
{
private int seed;
public MyRand(int seed)
{
this.seed = seed;
}
public int Next()
{
int next = (seed * 29 + 37) % 1000;
seed = next;
return next;
}
}
Call the following code:
MyRand rand = newMyRand(51);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(rand.Next());
}
The execution result is as follows:
Does the generated data look "random. This code briefly explains: we create an object of MyRand, then the constructor transmits a number of 51, which is assigned to seed, each time the Next method is called, a random number is calculated based on (seed * 29 + 37) % 1000. the random number is assigned to seed and the generated random number is returned. In this way, when Next () is called, seed is no longer 51, but the random number generated last time. It seems that every generated content is "random. Note: The purpose of "% 1000" to obtain the remaining budget is to ensure that the generated random number cannot exceed 1000.
Of course, no matter whether you run or every time I run it, the output result is the same random number, because according to the given initial data 51, we can infer in turn that all the "random numbers" generated below can be calculated. The initial data 51 is called the "random number seed", and the series of 516, 1, 66, 951, 616 ...... A number is called a random number sequence ". If we change 51 to 52, the following result will be displayed:
3. Good guy, begging for Seeds
So how can different "random number sequences" be generated every time a program is run? Because the time of each program execution may be different, we can use the current time as a "random number seed"
MyRand rand = newMyRand(Environment.TickCount);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(rand.Next());
}
Environment. TickCount is "the number of microseconds after the system starts ". In this way, the Environment. TickCount is unlikely to be the same each time the program runs (by who can start the program twice in a microsecond), so the random number generated each time is different.
Of course, if we put new MyRand (Environment. TickCount) in the for loop:
for (int i = 0; i < 100; i++)
{
MyRand rand = newMyRand(Environment.TickCount);
Console.WriteLine(rand.Next());
}
The running result becomes "many consecutive", and the principle is very simple: As the for loop body runs fast, Environment is used for each loop. tickCount is likely to be the same as the previous one (two lines of simple code run less than a millisecond-long event), because this time the "random number seed" is the same as the previous "random number seed, in this way, the first "random number" generated by Next () is the same. From "-320" to "-856", it takes a millisecond to run to "-856.
IV. Implementation of various languages
We can see that the. Net Random class has an int type parameter constructor:
Public Random(IntSeed)
Just like the MyRand we wrote, we accept a "random number seed ". The argument-free constructor we previously called isRandom(IntSeed) is constructed by passing the Environment. TickCount class. The Code is as follows:
Public Random():This(Environment. TickCount)
{
}
Now we finally understand the initial doubts.
Similarly, 10 random numbers generated in C/C ++ should not be called as follows:
int i;
for(i=0;i<10;i++)
{
srand( (unsigned)time( NULL ) );
printf("%d\n",rand());
}
However, we should:
srand ((unsigned) time (NULL)); // Set the current time to "random number seed"
int i;
for (i = 0; i <10; i ++)
{
printf ("% d \ n", rand ());
}
V. "wonderful" Java
Java learners may ask questions. In earlier Java versions, the following random numbers are generated in the same way as in. Net and C/C ++:
for(int i=0;i<100;i++)
{
Random rand = new Random();
System.out.println(rand.nextInt());
}
In earlier versions of Java, the implementation of the Rand class's no-argument constructor also uses the current time as the seed:
Public Random () {this (System. currentTimeMillis ());}
However, in a later version of Java, such as Java 1.8, the above "error" code execution is correct:
Why? Let's take a look at the implementation code of the Random no-argument constructor:
public Random()
{
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
privatestaticfinal AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
The current time is no longer used as the "random number seed", but the System. nanoTime () refers to the amount of time in the nanosecond level and performs an exclusive or operation with the atomic number AtomicLong calculated based on the number calculated by the last called constructor. For a detailed explanation of this Code, refer to this Article "decrypting random number generator (2) -- looking at linear equivalence algorithms from the java source code".
The most important thing is to use the static variable AtomicLong to record the seeds used to call the Random constructor every time. The next time you call the Random constructor, you should avoid the same as the last one.
Vi. Problems in high concurrency Systems
As we have analyzed above, for the random number generator that uses the system time as the "random number seed", if multiple random numbers are to be generated, therefore, you must share a "random number seed" to avoid generating duplicate random numbers within a short period of time. However, in some highly concurrent systems, a problem may occur if you do not pay attention to it. For example, a website generates a verification code on the server side using the following method:
Random rand = new Random ();
Int code = rand. Next ();
When the website concurrency is large, there may be many personal request verification codes within one millisecond, which may result in repeated verification codes requested by these people, it brings potential vulnerabilities to the system.
Another example is an article I saw today, "When Random is not enough: an online poker game lesson," because the seed of the random number generator is based on the server clock, hackers only need to synchronize their programs with the server clock to reduce the possible disorder to only 200,000. At that time, once a hacker knows five cards, he can quickly search for 200,000 possible disorder in real time and find the one in the game. So once the hacker knows the two and three cards in his hand, he can guess what cards will come when the cards are transferred, and the cards of other players. "
There are several solutions to this problem:
VII. True random number generator
According to our previous analysis, we know that these so-called random numbers are not really "random", but they only look random. Therefore, they are called "pseudo-random algorithms ". In some scenarios with high random requirements, some physical hardware will be used to collect real random physical parameters such as physical noise, cosmic rays, and quantum decay to generate real random numbers.
Of course, some smart people think of generating random numbers without adding the "random number generator" hardware. When we operate a computer, it is unpredictable to move the mouse or press the keyboard. It is unpredictable to execute External commands on the computer, process files, load data, and so on, therefore, changes in CPU operation speed, hard disk read/write behavior, and memory usage are unpredictable. Therefore, if the information is collected as the random number seed, the generated random number is unpredictable.
In Linux/Unix, the real random number generator "/dev/random" can be used. Its data mainly comes from hardware interruption information, but the random number generation speed is relatively slow.
In Windows, you can call the system's CryptGenRandom () function, it is calculated based on the current process Id, current thread Id, TickCount after the system starts, current time, high-performance counter value returned by QueryPerformanceCounter, user name, computer name, CPU counter value, and so on. Like "/dev/random", CryptGenRandom () is generated slowly and consumes a large amount of system resources.
Of course. you can also use the RNGCryptoServiceProvider class (System. security. according to the previous post on StackOverflow, RNGCryptoServiceProvider does not encapsulate the CryptGenRandom () function, but is similar to CryptGenRandom.
VIII. Summary
Someone may ask: since there is a "real random number generator" such as "/dev/random" and "CryptGenRandom ()", why do we need to provide and use a "fake" such as a pseudo random number "? As mentioned above, "/dev/random" and CryptGenRandom () are generated slowly and consume more performance. When the random number is less unpredictable, use the pseudo-random number algorithm, because the performance is relatively high. The real random number generator must be used for scenarios with high random number unpredictability requirements. The hardware equipment of the real random number generator must consider the cost, while "/dev/random" and "CryptGenRandom ()" have poor performance.
Everything is not perfect, not absolutely good or absolutely bad. This is the beautiful place of the Multi-world.