Pular para o conteúdo principal

Valores Aleatórios Simplificados

A partir do C++ 11, foi introduzido o header <random> com diversos facilitadores para suporte de geração de números aleatórios. A produção destes números é feita através da combinação de duas categorias de objetos: os geradores e os distribuidores. Os geradores, são responsáveis pela geração dos números, e os distribuidores são responsáveis pela transformação dos números gerados em algum tipo de distribuição de probabilidade. Como por exemplo, uma distribuição normal (aquela da Gaussiana) ou uma distribuição de Pareto (aquela do 80-20). As opções não faltam, como você pode ver nas referências, por exemplo:  http://www.cplusplus.com/reference/random/ ou http://en.cppreference.com/w/cpp/header/random.



Abaixo, um trecho utilizando os facilitadores citados para gerar 10 números inteiros aleatórios no intervalo fechado de 1 a 6:

[code language="cpp"]
//gerador
std::mt19937 engine;
//distribuidor
std::uniform_int_distribution<int> distribution(1, 6);
//[1, 6]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %1d\n", i + 1, distribution(engine));
[/code]

Note que foi instanciado um gerador (mt19937) e um distribuidor (uniform_int_distribution), para produzir o número, ocorreu um chamada para o distribuidor, onde seu argumento atual é o gerador (distribution(engine)). Ou seja, você pode combinar geradores e distribuidores que sejam compatíveis.

Se for o caso, é possível utilizar o std::bind para retornar um functor que faz aplicação de distribution em engine. No exemplo a seguir, este functor é o objeto dice:

[code language="cpp"]
std::mt19937 engine;
std::uniform_int_distribution<int> distribution(1, 6);
auto dice = std::bind(distribution, engine);
//[1, 6]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %1d\n", i + 1, dice());
[/code]

Ignorando a quantidade, uma das virtudes dos geradores aleatórios e suas distribuições no C++ Moderno é a sua qualidade, muito superior (e correto) ao tão polêmico rand herdado da linguagem C. Por outro lado, o modelo de uso não é simplista, se comparado ao rand. De fato, existe algumas propostas para a criação de um facilitador similar ao rand, que usa por trás esta infraestrutura do <random>. Você pode conferir o randint experimental no código fonte da libstdc++ ou da proposta para substituição do rand.  Desta forma, poderemos ter a seguinte usabilidade:

  • Sem definição dos limites ou boundaries:



[code language="cpp"]
//[0, 2147483647]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %10d\n", i + 1, rand_int());
[/code]


  • Com definição dos limites ou boundaries:



[code language="cpp"]
//[0, 255]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %3d\n", i + 1, rand_int(0, 255));
[/code]

Não necessariamente precisa ser de uma futura versão da biblioteca padrão ou limitada a um compilador. Ela pode ser criada por você, ou se preferir reutilizar a nossa implementação inspirada no que dissemos até o momento. A seguir, um código em C++ Moderno que implementa o rand_int e o seed_rand para o tipo int, isto nos trás uma usabilidade similar ao rand e ao srand.

https://gist.github.com/fabiogaluppo/0fb6952e6a3bb1ca533762bbf897eb33

O código integral pode ser acessado por aqui: https://github.com/SimplyCpp/examples/blob/master/random_util/random_util.hpp

Na implementação de random_util.hpp foi usado o thread_local, para o gerador e para o distribuidor. O thread_local foi introduzido no C++ 11 para suportar de forma independente de plataforma o recurso de thread-local storage dos sistemas operacionais que possuem este modelo de memória estática isolada por thread. A idéia é que cada thread possua seu gerador e distribuição independentes e não compartilhem estados entre si, evitando problemas de concorrência. Por outro lado, cada thread que usa este facilitador consumirá uma quantidade de memória. Um trade-off justo, não acha?

Ok, resolvemos o problema. Temos os facilitadores rand_int e seed_rand. Agora é só usá-los. :)

Não é bem por aí :|, apesar de ser um boa ajuda  ;). No C++ Moderno, podemos fazer uso dos templates e generalizar este facilitadores para diversos tipos de números inteiros e reais. Assim podemos disponibilizar as seguintes funcionalidades:

  • Para tipos inteiros (int, short, long, ...) com intervalos fechados



[code language="cpp"]
//[0, 2147483647]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %10d\n", i + 1, rand_int<int>());

//[1, 5]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %1d\n", i + 1, rand_int<int>(1, 5));

//[-1, 1]
seed_rand();
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %2d\n", i + 1, rand_int<int>(-1, 1));

//[0, 2147483647]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %10d\n", i + 1, rand_int());

//[0, 255]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %3d\n", i + 1, rand_int(0, 255));

//[0, 65535]
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %5d\n", i + 1, rand_int<unsigned short>());
[/code]


  • Para tipos reais (float, double, ...) com intervalos semi-abertos a direita



[code language="cpp"]
//[0.000000, 1.000000)
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %f\n", i + 1, rand_real<double>());

//[-0.5, 0.5)
seed_rand();
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %9.6f\n", i + 1, rand_real<double>(-0.5, 0.5));

//[0.000000, 1.000000)
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %f\n", i + 1, rand_double());

//[-10.000000, 10.000000)
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %10.6f\n", i + 1, rand_double(-10, 10));

//[0.000000, 1.000000)
unsigned int seed = static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count());
seed_rand(seed);
for (int i = 0; i < 10; ++i)
std::printf("%2d -> %f\n", i + 1, rand_real<float>());
[/code]

O código integral das bibliotecas podem ser acessados por aqui:

  • Para tipos inteiros


https://github.com/SimplyCpp/examples/blob/master/random_util/random_int_util.hpp

  • Para tipos reais


https://github.com/SimplyCpp/examples/blob/master/random_util/random_real_util.hpp

Ambas são relacionadas a distribuição, e dependem da geradora:

https://github.com/SimplyCpp/examples/blob/master/random_util/random_engine_util.hpp

Abaixo uma execução dos exemplos compilados no Visual C++ (msvc) e no clang++:

randomutil-msvc

randomutil-clang

Referências:

http://www.cplusplus.com/reference/random/

http://en.cppreference.com/w/cpp/header/random

Fontes:

https://github.com/SimplyCpp/examples/tree/master/random_util

Comentários

Postagens mais visitadas deste blog

Mestre Iota

Iota é a nona letra do alfabeto grego, ela é equivalente à letra i do nosso alfabeto. Por convenção ou hábito, utilizamos a letra i na programação para indicar algum tipo de incrementador, como por exemplo, em um for-loop . https://gist.github.com/fabiogaluppo/a23894ae743f7dd29274 Curiosamente, iota , como identificador, também é utilizado na programação para indicar uma sequência finita e consecutiva de números inteiros, como por exemplo, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. Inclusive, originalmente na STL existia a função iota , inspirada pela linguagem de programação APL , você pode conferir neste link: http://www.sgi.com/tech/stl/iota.html

Policy-based design: log writer

Policy-based design Vamos neste artigo dar mais uma pincelada no Policy-based design . Vamos fazer como exemplo uma classe de log. Como este é só um exemplo, não vamos considerar múltiplos parâmetros no log, mas somente uma string, assim não fugiremos do assunto. Uma das coisas mais importantes neste tipo de design é o desacoplamento. Ele é uma excelente alternativa ao uso de interfaces por duas razões: Não gera chamadas virtuais (ou um nível de indireção em tempo de execução) Duck typing ( https://pt.wikipedia.org/wiki/Duck_typing ) Eu gosto bastante desse tipo de design, já usado aqui: http://simplycpp.com/2016/02/05/leitura-de-configuracao-em-c/