Como gerar números aleatórios em C++: não use rand()!

numeros aleatorios em C++ ao lado de dados

A maioria das pessoas com alguma experiência em programação em C e C++, quando quer gerar números aleatórios em C++ pensa logo em rand(), todavia esses métodos não são a melhor opção no C++ moderno! Ao invés disso, o C++ moderno nos oferece ferramentas muito mais adequadas para gerar números aleatórios: motores de números aleatórios, diferentes distribuições (uniforme, binomial, bernoulli etc) e algumas outras coisas das quais não falarei nesse artigo.

Antes de passar ao modo correto de se gerar números aleatórios em C++, vejamos porque não se deve usar rand().

referencias de livros em C++
Referências

Tomei como base para escrever esse artigo a primeira metade do Capítulo 23 do livro C++ Profissional, de Marc Gregoire (que recomendo bastante) e o Capítulo 17.4.1 do livro C++ Primer, de Stanley B. Lippman (este é um pouco mais simples, mas também excelente – recomendo-o para iniciantes).

Desvantagens do rand()

A seguir estão as principais razões pelas quais eu não recomendo que você use o rand() para gerar números aleatórios.

Faixa de geração de números aleatórios fixa

A função rand(), que deve ser sempre “preparada” pela função srand() – responsável por gerar a semente que será usada pela rand() para gerar os números aleatórios -, sempre gera valores na faixa de 0 a RAND_MAX (que segundo o Cppreference é um inteiro cujo valor é definido pela implementação do seu compilador, com valor mínimo de 32767, mas que pode ter o valor de 2147483647 – isso é bastante comum).

Assim, se você quiser gerar um valor aleatório de 0 a 10, por exemplo, é preciso fazer uma manipulação extra do número gerado, algo como (rand() % (maxValue - minValue)) + minValue, para pode “re-enquadrar” o número aleatório gerado pelo rand(). A seguir temos um exemplo que mostra o uso do rand() para gerar números de tipo double na faixa [0, 1] que tomei do site Cppreference.

#include <climits>
#include <cstdlib>
#include <ctime>
#include <iostream>
 
int main()
{
    // use current time as seed for random generator
    std::srand(std::time(NULL));
 
    std::cout << "RAND_MAX: " << RAND_MAX << '\n'
              << "INT_MAX: " << INT_MAX << '\n'
              << "Random value on [0,1]: "
              << static_cast<double>(std::rand()) / RAND_MAX << '\n';
}

RAND_MAX: 2147483647
INT_MAX: 2147483647
Random value on [0,1]: 0.618608

Má qualidade na geração de números aleatórios pequenos

Quando se precisar gerar números aleatórios em C++ usando rand() que pertencem a uma faixa de valores baixos, como números de 0 a 10, por exemplo, a qualidade da aleatoriedade dos números gerados pelo rand() pode deixar a desejar, pois esse gerador de números pseudo-aleatórios não é capaz de randomizar muito bem os bits menos significativos dos números.

O que usar para gerar números aleatórios em C++?

Já que não posso usar o rand(), o que devo usar então para gerar meus números aleatórios em C++? O que você precisa fazer é bastante simples, e eu vou explicar para você o passo-a-passo do que fazer, mas antes irei falar rapidinho dos motores de números aleatórios (ou random number engines, em inglês).

Motores de números aleatórios (random number engines)

Os motores de números aleatórios são as ferramentas que irão, de fato, gerar os seus números. No C++ existem 4 principais motores que listarei a seguir, dos quais falarei dos principais: o random_device e o mersenne_twister_engine.

  1. random_device
  2. mersenne_twister_engine
  3. linear_congruential_engine
  4. subtract_with_carry_engine

random_device

O random_device não é um gerador de números aleatórios baseado em software, mas na verdade ele gera os números aleatórios através de algum dispositivo externo conectado ao computador que se baseia, por sua vez, em um mecanismo do mundo natural como gerador de aleatoriedade. Esse dispositivo pode se basear, por exemplo, em um tipo especial de ruido eletrônico de natureza aleatória para gerar seus números aleatórios, ou no decaimento de um isótopo de plutônio (eu espero que não, pelo bem da sua saúde 🤣).

Os random_device possuem um método especial chamado entropy que permite detectar se há de fato um dispositivo externo conectado ao programa; se não houver, entropy() retornará 0. Veja um exemplo simples do uso de um random_device a seguir. Note que se não houver dispositivo externo em uso, a implementação pode escolher um gerador pseudo-aleatório baseado em software dentre os 3 listados acima para substituir o mecanismo de base do random_device – ou seja, o random_device agirá como se fosse um mersenne_twister_engine ou um linear_congruential_engine, por exemplo.

random_device rnd;
if (rnd.entropy()) {
    std::cout << "Dispositivo externo gerador de aleatoriedade detectado!" << std::endl;
}

Mersenne_twister_engine

O principal dos geradores de números pseudo-aleatórios é o mersenne_twister_engine. Ele possui a melhor qualidade dentre todos os 3 que são baseados em software e é muito fácil de usar, pois a biblioteca padrão já fornece, no header <random>, um tipo chamado mt19937 que é um mersenne_twister_engine pré-configurado com bons parâmetros. Se esse tipo não existisse, você teria que passar 14 argumentos de template para pode instanciar um mersene_twister – cansei só de imaginar!

Como gerar números aleatórios em C++ usando os motores?

Agora sim, irei falar o passo-a-passo de como gerar números aleatórios em C++ usando os motores de geração e as distribuições de números. Aí vai!

  1. Crie um objeto de tipo random_device (chamarei-lhe seeder);
  2. Use o objeto criado no passo 1 para criar uma semente (o que gerará o ponto de partida para os seus motores pseudo-aleatórios) se entropy() não for zero (ou seja, se você realmente tiver um dispositivo externo contectado ao programa); se entropy() for zero, use uma semente baseada no tempo: time(nullptr);
  3. Crie um motor de números pseudo-aleatórios (usarei o mersenne_twister_engine) passando-lhe como argumento de criação a semente do passo anterior;
  4. Crie uma distribuição de sua escolha e configure-a adequadamente (usarei uma distribuição uniforme)
  5. Use a distribuição passando-lhe o motor gerado no passo 3 para gerar o número aleatório. Neste passo você pode usar o std::bind para “fixar” como argumento da distribuição o seu motor aleatório, o que permite gerar números aleatórios mais facilmente (veja o exemplo abaixo)

Exemplo 1 – gerando números aleatórios em C++ com mt19937

#include <random>
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>

using namespace std;

int main() {
  random_device seeder;
  // use the device as seed only if we actually have one
  const auto seed { seeder.entropy() ? seeder() : time(nullptr) };
  // set up the mt19937 engine
  mt19937 randomEngine { static_cast<mt19937::result_type>(seed) };

  // Generates numbers in the interval [1, 99]
  uniform_int_distribution<int> uniformDistribution { 1, 99 };

  // Binding the engine to the distribution to be able
  // to generate numbers without needing to do generator(randomEngine)
  auto generator { bind(uniformDistribution, randomEngine) };
  
  vector<int> values(10);
  // generate from the stl
  generate(begin(values), end(values), generator);
  
  for (auto i : values) { cout << i << " "; }
}

42 80 1 23 49 82 25 69 37 44


Conclusão

Neste artigo falei para você sobre porque não usar o rand() para gerar números aleatórios em C++ (baixa qualidade dos números gerados em faixas de valores baixos e necessidade de manipulação extra do valor gerado para adequá-lo à faixa de valores desejada), e expliquei qual é a alternativa adequada a se usar no C++ moderno: motores de números pseudo-aleatórios, random_device, distribuições – tudo isto junto.

Gostou do artigo? Então inscreva-se na nossa newsletter para não perder nenhum dos nossos artigos e materiais exclusivos para inscritos 😉

Foto de perfil de Emanoel

Sou apaixonado por tecnologia, literatura e também filosofia. O cultivo dessas paixões ao longo da minha trajetória me inspiraram a compartilhar aquilo que aprendo com os outros da maneira mais clara que eu possa. Sou formado em Engenharia Elétrica no Brasil, e também sou engenheiro formado na França. Trabalho atualmente como programador C++ em uma multinacional francesa, uma das maiores empresas de TI do mundo.

Leave a Reply

Your email address will not be published. Required fields are marked *