In the previous blog, we learned about the real random number generator based on physical phenomena, however, the real random number generation is slow, for the actual computation needs, the random number in the computer is generated by the program algorithm, that is, some formula function, but for the same random seed and function, the resulting random sequence is certain, Therefore, the resulting random number is predictable and cyclical, not a true random number, and is therefore called a pseudo-random number (Pseudo random numbers).
However, do not look down to the pseudo-word despise, which is also learned, seemingly a few simple formula may be the predecessors have worked hard for several generations of results, related research can write several books! By the way, the Asian only Turing prize winner, Andrew Chi-chi, is studying pseudo-random number generation (the pseudo random numbers generating theory). Here, I highlight two common algorithms: the same congruential (Congruential method) and the Mason rotation algorithm (Mersenne twister)
1, with congruential
The same congruential (Congruential method) is a very common method of generating random numbers, which is used in many programming languages, the most obvious is Java, and Java.util.Random class is one of the congruence method--linear with congruential (Linear Congruential method), in addition to the multiply with congruential (multiplicative congruential method) and mixed with congruential (Mixed congruential method). OK, now let's open the source code of Java and look at the true colors of linear and congruential!
Enter Java.util.Random in Eclipse and press F3 to go to the source code for the Random class:
First, we see an explanation for this:
The translation comes here:
An implementation of this class is used to generate a bunch of pseudo-random numbers. This class uses a 48-bit seed that is modified by the linear congruence formula to generate random numbers. (see Donald Kunth, "Art of Computer Programming" Vol. II, chapter 3.2.1)
Obviously, the random class of Java uses the linear same Yufarai to get the stochastic number.
Then, looking down, we found its constructor and several methods, which contain the process of acquiring a 48-bit seed:
Java code
- /**
- * Creates a new random number generator. This constructor sets
- * The seed of the random number generator to a value very likely
- * To is distinct from any and invocation of this constructor.
- */
- Public Random () {
- This (Seeduniquifier () ^ system.nanotime ());
- }
- private static Long Seeduniquifier () {
- L ' Ecuyer, "Tables of Linear congruential generators of
- Different Sizes and good Lattice Structure ", 1999
- for (;;) {
- Long current = Seeduniquifier.get ();
- Long next = current * 181783497276652981L;
- if (Seeduniquifier.compareandset (current, next))
- return next;
- }
- }
- private static final Atomiclong Seeduniquifier
- = new Atomiclong (8682522807148012L);
- Public Random (long Seed) {
- if (getclass () = = Random.class)
- This.seed = new Atomiclong (initialscramble (Seed));
- else {
- Subclass might have overriden Setseed
- This.seed = new Atomiclong ();
- Setseed (seed);
- }
- }
- private static long initialscramble (long seed) {
- return (seed ^ multiplier) & mask;
- }
- 。。。
The System.nanotime () method is used here to get a nanosecond-level amount of time, participate in the composition of the 48-bit seed, and then perform a very perverted operation-multiplying by 181783497276652981L, until the result is the same before and after a multiplication- To further increase the randomness, the nanotime here can be considered a true random number, but it is necessary to mention that Nanotime is different from the CurrentTime method we commonly use, returning from the January 1, 1970 to the present time, but a random number-- It is only used to calculate a time period before and after comparison, such as the run time of one line of code, the time of database import, etc., but not to calculate which day it is today.
Well, now I have to admire the engineer's metamorphosis: So far, the program has been at least three times random:
1. Get a long shape number as "initial seed" (system default is 8682522807148012L)
2, constantly multiplied with a perverted number--181783497276652981l (days know these numbers are not engineers casually roll out of the keyboard-.-) until a certain time before and after the value is equal
3, with the system random out of the Nanotime value for the difference or operation, to obtain the final seed
Looking down again, is the method we used to get random numbers, I first found the most commonly used nextint () function, the code is as follows:
Java code
- public int Nextint () {
- Return next (32);
- }
The code is simple and jumps directly to the next function:
Java code
- protected int Next (int bits) {
- Long Oldseed, nextseed;
- Atomiclong seed = this.seed;
- do {
- Oldseed = Seed.get ();
- Nextseed = (Oldseed * multiplier + addend) & mask;
- } while (!seed.compareandset (Oldseed, nextseed));
- return (int) (Nextseed >>> (48-bits));
- }
OK, congratulations on how, because we've gone deep into the core of the linear with congruential--yes, that's just a few lines of code!
Before analyzing this piece of code, let's briefly introduce the linear and congruential.
In order to make the result of an expression less than a certain value in the program, we often take the operation of the remainder, which results in the same divisor, which is called the same congruential (Congruential method).
Linear congruential is a very old random number generation algorithm, and its mathematical form is as follows:
Xn+1 = (a*xn+c) (mod m)
which
M>0,0<a<m,0<c<m
Here xn this sequence generates a series of random numbers, X0 is a seed. The quality of random number is related to the selection of three parameters of M,a,c. These random numbers are not really random, but rather meet in a certain period of random distribution, the period of the longest is M. According to Hull-dobell theorem, when and only if:
1. C and M mutual-vegetarian;
2. A-1 can be divisible by the mass factor of all m;
3. When M is an integer multiple of 4, A-1 is also an integral multiple of 4
, the period is M. So M is generally set very large to extend the cycle.
Now let's go back and look at the procedure we've just taken, and note this line of code:
Java code
- Nextseed = (Oldseed * multiplier + addend) & mask;
and xn+1= (a*xn+c) (mod m) in the form much like there are wood there!
Yes, this line of code is applied to the linear same congruential formula! But there's one more question: Why don't you see the rest symbol? Hey, let's take a look at the numerical declaration of the three variables:
Java code
- Private static final long multiplier = 0X5DEECE66DL;
- Private static final long addend = 0xBL;
- Private static final Long mask = (1L << 48)-1;
multiplier and addend , respectively, represent a and C in the formula, well understood, but what does mask mean? In fact, X & [(1L << 48) –1] is equivalent to X (mod 2^48). The explanations are as follows:
X for the N-Power of 2, since the divisor is a power of N to 2, such as:
0001,0010,0100,1000 ....
The equivalent of moving the binary form of x to the right n-bit, when moving to the right of the decimal point is the remainder, such as:
13 = 1101 8 = 1000
13/8 = 1.101, so the 101 to the right of the decimal point is the remainder, and the decimal is 5.
However, whether it is C or Java, the number of bit operations removed is clearly gone. (What, you say in the CF register?) Well, too high-end point, in fact there is a more power to the method) what is the best way to protect the data that is about to pass away?
Learning the mask above, we might as well try to reduce the power of the 2 N to one:
0000,0001,0011,0111,01111,011111 ...
What, is it enlightening?
We know that a certain number (limited to 0 and 1) and 1 are operated with (&), and the result is itself; and the result of the operation with 0 is always 0, i.e.:
A & 1 = A, A & 0 = 0
What we want to achieve with X for 2^n is understood as:
1. All the bits higher than the 2^N bit (including the 2^n) are all 0
2. All bits lower than 2^n remain intact
Thus, X & (2^n-1) is equivalent to the X (mod 2^n) operation, as well as the example of 13 and 8:
1101% = 0101 1101 & 0111 = 0101
The results are consistent.
Hehe, to understand this and the meaning of the operation, I think the above line of code meaning should be very clear, is the linear congruence formula direct, where a = 0x5deece66dl, c = 0xBL, M = 2^48, you can get a 48-bit random number, and this cautious engineer iterative, Increase the randomness of the results. If the result is shifted again, the random number of the specified number of digits can be obtained.
Next we look at a more common function-the nextint with parameter n:
Java code
- public int Nextint (int n) {
- if (n <= 0)
- throw new IllegalArgumentException ("N must be Positive");
- if ((N &-N) = = n)//i.e., n is a power of 2
- return (int) ((n * (long) Next) >> 31);
- int bits, Val;
- do {
- bits = Next (31);
- val = bits% n;
- } while (Bits-val + (n-1) < 0);
- return Val;
- }
Obviously, the basic idea here is the same, first call the next function to generate a 31-bit random number (int type range), and then the parameter N to judge, if N is exactly 2 of the power, then the direct shift can get the desired result; if it is not a power of 2, then about N, then the result in the [ 0,n) in the range. In addition, the purpose of the Do-while statement should be to prevent negative results.
You may wonder why (N &-N) = = n can determine whether a number is 2 power, in fact, I also studied a few to understand, in fact, this is mainly related to the characteristics of the complement:
As we all know, negative numbers in the computer use the complement of stored (do not know what is the complement of their own Baidu evil complement), to cite a few groups of examples:
2:0000 0010-2:1111 1110
8:0000 1000-8:1111 1000
18:0001 0010-18:1110 1110
20:0001 0100-20:1110 1100
Do not know whether you notice that the complement has a characteristic, that is, can be two opposite number N and-N, and only the lowest one is 1 the same number and all 1, and the lower bits are all 0, the higher bits are different. So the two-digit bitwise AND operation only one is 1, and can satisfy this result is still n is only the original only one is 1 of the number, that is, the number of the power of the right is exactly 2.
But personally, there is a better way to judge the power of the 2:
N & (n-1) = = 0
Interested can also study the ^o^.
OK, linear with congruential introduced to this, the following is a brief introduction to the other Congruential--by the same congruential (multiplicative congruential method).
The linear and congruential in the above, mainly used to generate integers, and some scenarios, such as scientific research, often only need (0,1) between the decimal, at this time, multiply the same congruential is a better choice, its basic formula and linear with congruential very much like:
Xn+1= (A*XN) (mod m)
In fact, it only c=0 the linear formula. Just to get a decimal, we do one more step:
Yn = xn/m
Since Xn is the remainder of M, the value of yn is between 0 and 1, thus to the random sequence on the (0,1) interval.
In addition, there are mixed with congruential, two times with congruential, three times with similar methods such as congruential, the formula is similar, but also each has its merits and demerits, not in detail in this article.
The same congruential advantage in the computation speed, the memory consumption is low. However, because the adjacent random number is not independent, the sequence association is larger. Therefore, the application of high quality of random numbers, especially in many fields of research, is not suitable for this method.
Don't go away, the next blog introduces a more force-based algorithm-Mason rotation algorithm (Mersenne Twister), continue to pay attention to AH!
First, decryption random number generator (2)--from Java source code to see the linear congruence algorithm (turn)