Random C ++ Function

In the midst of the creation of STL and the fierce war for the C ++ standard, a number of programmers have developed their own cross-platform class library, providing developers with tools for solving everyday tasks, such as data processing, algorithms, working with files, etc. This library is called Boost. The project is so successful that Boost features are borrowed and fit into the language standard, starting with C ++ 11. One of these additions is the improved work with random numbers.

Pseudo-random generator

The rand () and srand () functions are school-level and are suitable for writing simple programs. The disadvantage of these functions is the generation of an insufficiently good sequence of pseudorandom numbers (picture above). Also, the capabilities of simple functions are not enough when developing complex projects.

To solve the problem, random number generators (RNG) were invented. With their appearance, work on generating many types of data, both pseudo and truly random, has improved significantly. An example of generating truly random numbers is the noise in the picture below.

A truly random generator

Pseudo random number generator

Dice as a symbol of chance

The traditional midrange creation algorithm combined at the same time an algorithm for creating unpredictable bits and turning them into a sequence of numbers. In the random C ++ library, which is part of Boost, these two mechanisms are separated. Now the generation of random numbers and the creation of distribution (sequence) from them occurs separately. Using distribution is perfectly logical. Because a random number without a specific context does not make sense and is difficult to use. Let's write a simple function that rolls a bone:

#include <random> int roll_a_dice() { std::default_random_engine e{}; //   std::uniform_int_distribution<int> d{1, 6} //       return d(e); } 

A typical mistake of those who study random is to ignore the creation of the distribution and go directly to the creation of random numbers in the way they are used to. For example, consider the function described above.

 return 1 + e() % 6; 

Some consider its use to be acceptable. Indeed, C ++ allows this to work. However, the creators of the Boost library and C ++ 11 standards are strongly advised not to. In the best case, it will turn out to be just a bad-looking code, and in the worst, it will be a working code that makes mistakes that are very difficult to catch. Using distributions ensures that the programmer gets what he expects.

Initialization of the generator and seed

The stage of declaring, defining and creating entities is often seen as something not worthy of special attention. But insufficiently thoughtful initialization of the random number generator can affect its proper operation.

 std::default_random_engine e1; //     std::default_random_engine e2{}; //     

The first 2 initializations are equivalent. And for the most part, they relate to taste or to standards for writing beautiful code. But the next initialization is fundamentally different.

 std::default_random_engine e3{31255}; //  31255 

"31255" - this is called seed (seed, source) - the number on the basis of which the generator creates random numbers. The key point here is that with this initialization, the type of seed must be the same or reducible to the type with which the generator works. This type is available through the decltype (e ()) construct, or result_of, or typename.

Why does the generator create the same sequence?

When a program is run several times, the generator always creates the same sequence of numbers, if its initialization does not change, that is, the definition of the generator occurs in the same way from start to start of the program. On the one hand, such self-reproduction of numbers by a generator is useful, for example, in debugging. On the other hand, it is undesirable and can cause problems.

Accordingly, to avoid repeating the sequence of numbers, the generator must be initialized with different values ​​each time the program starts. Just for these purposes, you can use seed. The standard way to initialize the PRNG is to pass it the value of time (0) from the ctime header file as a seed. That is, the generator will be initialized with a value equal to the number of seconds elapsed since January 1, 00 hours 00 minutes 00 seconds, 1970 UTC.

PRNG initialization by another generator

Initializing time may not be enough to solve a number of problems. Then you can determine the PRNG through another generator. Here I would like to digress and talk about one powerful tool that allows you to create truly random numbers.

Random_device - true random number generator

Random numbers

All pseudo random number generators are deterministic. That is, they have a definition. Or in other words, getting random numbers is based on mathematical algorithms. Random_device is non-deterministic. He creates numbers based on stochastic (random from other Greek) processes. Such processes can be changes in the phase or amplitude of current oscillations, molecular lattice vibrations, air mass movements in the atmosphere, etc.

Obviously, not every computer and not every system can have the ability to get a random number based on a stochastic process. Therefore, resorting to using random_device is only necessary. Its operation may differ from system to system, from computer to computer, and may even be inaccessible. Therefore, when using a true random number generator, error handling must be foreseen.

Using random_device as seed for PRNG

 std::random_device rd{}; std::default_random_engine e{ rd() }; 

There is nothing fundamentally new in this code. In this case, with each start, the PRNG is initialized with random values, which are created by the generator of truly random numbers rd.

It is also worth noting that the generator initialization value can be reset at any time:

 e.seed(15027); //   e.seed(); //    e.seed( rd() ); //   

To summarize: generators and distributions

A generator is an object that allows you to create different equiprobable numbers.

Distribution (distirbution) is an object that converts a sequence of numbers created by a generator into distributions according to a certain law, for example:

  • uniform
  • normal - Gaussian (normal);
  • binomial, etc.

Consider the generators of the standard C ++ library.

  1. It is enough for beginners to use default_random_engine, leaving the choice of the generator to the library. A generator will be selected based on a combination of factors such as performance, size, quality of randomness.
  2. For advanced users, the library provides 9 pre-configured generators. They differ greatly in performance and size, but at the same time, their quality of work has been subjected to serious tests. A generator called Mersenne twister engines and its instances mt19937 (creating 32-bit numbers) and mt19937_64 (creating 64-bit numbers) are often used. The generator is an optimal combination of speed and degree of randomness. For most arising tasks it will be enough.
  3. For experts, the library provides configurable generator templates that allow you to create additional types of generators.
Normal distribution

Consider the key aspects of distributions. In the language standard there are 20 of them. In the example above, we used the uniform distribution of the random C ++ library in the range [a, b] for integers - uniform_int_distribution. The same distribution can be used for real numbers: uniform_real_distribution with the same parameters a and b of the number generation interval. Moreover, the boundaries of the gap are included, that is, [a, b]. It makes no sense to list all 20 distributions and repeat the C ++ documentation in the article.

It should be noted that each distribution has its own set of parameters. For a uniform distribution, this is a range from a to b. And for geometric (geometric_distribution) parameter is the probability of success p.

Most distributions are defined as a class template for which the type of sequence values ​​is a parameter. However, some distributions create sequences of only int values ​​or only real values. Or, for example, a Bernoulli sequence (bernoulli_distribution) providing values ​​of type bool. As with the RNG, the library user can create their own distributions and use with the built-in generators or with the generators they create.

Gamma distribution

The library’s capabilities are not limited to this. They are much wider. But the information provided is sufficient for use and a basic understanding of the random number generator in C ++.

Quick reference: Random in .Net style

The .Net framework also has a Random class for creating pseudo-random numbers. Consider the example of generating a random number C ++ / CLI.

For those who work in Visual Studio and cannot understand why the System namespace is undefined.

To work with .net, you need a CLR connection. This is done in two ways. 1) Creating a project not with a console console app, but with CLR support — Console application CLR. 2) Connect CLR support in the settings of an already created project: project properties (project tab, not service ") -> configuration -> general -> default values ​​-> select" Support for the common language runtime (CLR) in the drop-down list of the item "Support for the common language runtime (CLR)".

 #include "stdafx.h" #include <iostream> //using namespace System; int main(array<System::String ^> ^args) { System::Random ^rnd1 = gcnew System::Random(); // ,      std::cout << rnd1->Next() << "\n"; //    int upper = 50; std::cout << rnd1->Next(upper) << "\n"; //      upper int a = -1000; int b = -500; std::cout << rnd1->Next(a, b) << "\n"; //     [a, b] int seed = 13977; System::Random ^rnd2 = gcnew System::Random(seed); //   seed std::cout << rnd2->Next(500, 1000) << "\n"; //          . std::cout << std::endl; return 0; } 

In this case, all the work is due to the Random Next C ++ / CLI function.

It is worth noting that .net is a large library with extensive features and uses its own version of the language called C ++ / CLI from Common Language Infrastructure. In general, this is a C ++ extension for the .Net platform.

Let's look at a few examples at the end to better understand working with random numbers.

 #include <iostream> #include <random> #include <ctime> int main() { std::mt19937 e1; e1.seed(time(0)); std::cout << e1() << std::endl; std::mt19937 e2(time(0)); std::mt19937 e3{}; std::uniform_int_distribution<int> uid1(5, 10), uid2(1, 6); std::cout << uid1(e2) << ", " << uid2(e3) << std::endl; std::default_random_engine e4{}; std::uniform_real_distribution<double> urd(0.5, 1.2); std::normal_distribution<double> nd(5.0, 2.0); //     5.0    2.0 std::cout << urd(e4) << ", " << nd(e4) << std::endl; std::cout << std::endl; system("pause"); return 0; } 

Conclusion

Any technologies and methods are constantly being developed and improved. This happened with the random (random) number generation mechanism rand (), which is outdated and no longer satisfies modern requirements. There is a random library in the STL, in the .Net Framework - the Random class for working with random numbers. The use of rand should be abandoned in favor of new methods, because they correspond to modern programming paradigms, and old methods will be derived from the standard.

Source: https://habr.com/ru/post/K16919/


All Articles