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:

//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));

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:

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());

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:
//[0, 2147483647]
for (int i = 0; i < 10; ++i)
    std::printf("%2d -> %10d\n", i + 1, rand_int());
  • Com definição dos limites ou boundaries:
//[0, 255]
for (int i = 0; i < 10; ++i)
    std::printf("%2d -> %3d\n", i + 1, rand_int(0, 255)); 

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.

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
//[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>());
  • Para tipos reais (float, double, …) com intervalos semi-abertos a direita
//[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>());

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

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s