Menu

Números aleatórios em C: rand, srand e gerar um número em um intervalo

Como gerar números aleatórios em C com rand() e RAND_MAX, por que você semeia com srand(time(NULL)) exatamente uma vez, mapear em um intervalo com % e o pequeno viés que isso introduz, doubles aleatórios e sequências reprodutíveis.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Os números aleatórios do C vêm de duas funções em <stdlib.h>: rand(), que produz o próximo valor, e srand(), que define o ponto de partida. Eles não são verdadeiramente aleatórios - são uma sequência pseudoaleatória, calculada deterministicamente a partir de uma semente - o que é uma limitação para criptografia e um recurso para testes.

rand() e RAND_MAX

rand() devolve um int em algum lugar entre 0 e RAND_MAX, inclusive. RAND_MAX é uma macro garantidamente de pelo menos 32767; no Linux e no macOS ela é 2147483647.

Execute isso duas vezes. Os números são idênticos nas duas - e isso não é um bug.

Semeando com srand

Sem uma chamada a srand, a sequência se comporta como se você tivesse chamado srand(1). Mesma semente, mesma sequência, toda execução. Para obter números diferentes a cada execução, semeie com algo que muda - convencionalmente, a hora atual:

time(NULL), de <time.h>, devolve os segundos desde o início de 1970, então cada execução recebe uma semente diferente. A conversão para unsigned int silencia um aviso sobre estreitar o time_t.

Três regras sobre semear, todas as quais as pessoas erram:

Semeie exatamente uma vez, no começo de main. Chamar srand antes de cada rand() é o antipadrão clássico - dentro de um laço que termina em menos de um segundo, time(NULL) devolve o mesmo valor a cada iteração, então você resemeia com o mesmo número e rand() devolve o mesmo primeiro valor toda vez. A saída é uma coluna de números "aleatórios" idênticos.

Não resemeie para "melhorar" a aleatoriedade. A qualidade do gerador vem do avanço do seu estado interno; reiniciar esse estado joga a sequência fora.

time(NULL) tem resolução de um segundo. Dois programas lançados no mesmo segundo recebem a mesma sequência. Isso serve para um jogo e é errado para qualquer coisa em que a independência importa.

Um número em um intervalo

O idioma padrão usa o operador de resto:

rand() % n            /* 0 ate n-1 */
rand() % n + min      /* min ate min+n-1 */

Para obter de min até max inclusive, a quantidade de valores possíveis é max - min + 1:

O + 1 é onde moram os erros de deslocamento por um. rand() % 6 dá de 0 a 5, então um lançamento de dado é rand() % 6 + 1. Escrever rand() % 7 + 1 para "incluir o 6" te dá um dado de sete faces.

A nota honesta sobre o viés do módulo

rand() % n não é perfeitamente uniforme a menos que n divida RAND_MAX + 1 exatamente.

Pense com números pequenos. Se RAND_MAX fosse 9 - então rand() devolve de 0 a 9, dez valores igualmente prováveis - rand() % 3 mapeia 0,3,6,9 para 0; 1,4,7 para 1; e 2,5,8 para 2. O resultado 0 ocorre de quatro maneiras em dez, e os resultados 1 e 2 de três maneiras cada. O zero é 33% mais provável.

O mesmo desvio existe com o RAND_MAX real, só que bem menor: os valores sobrando são os primeiros (RAND_MAX + 1) % n resultados, cada um ganhando uma chance extra em cerca de 2,1 bilhões. Para um lançamento de dado, um baralho embaralhado ou uma simulação, isso é imensurável - use % e siga em frente.

Quando isso importa - trabalho estatístico, qualquer coisa ligada a segurança - rejeite os valores sobrando em vez de dobrá-los para dentro:

O laço joga fora a pequena faixa de valores que causaria o desvio e sorteia de novo. Ele termina rápido - a fatia rejeitada é uma fração ínfima do todo.

Para aleatoriedade genuinamente sensível a segurança, rand() é a ferramenta errada por mais cuidado que se tome: use arc4random_buf no macOS e no BSD, getrandom() no Linux, ou BCryptGenRandom no Windows.

Doubles aleatórios

Divida por RAND_MAX para cair em [0.0, 1.0] e depois escale:

A conversão em (double) rand() é essencial. Sem ela, rand() / RAND_MAX é divisão inteira e resulta em 0 quase sempre, e 1 na chance de um em dois bilhões de acertar o máximo - um bug que parece "meus doubles aleatórios são todos zero". Veja conversão de tipos para entender por quê.

Sequências reprodutíveis

Uma semente fixa dá a sequência idêntica em toda execução, que é exatamente o que você quer para um teste, uma sessão de depuração ou um jogo com códigos de fase compartilháveis:

A semente 42 produz os mesmos cinco números toda vez que é usada, nesta execução e em qualquer outra com a mesma biblioteca. Essa reprodutibilidade é por que uma simulação deveria permitir escolher a semente: rode com o relógio normalmente e passe uma semente fixa quando for reproduzir um bug.

Uma ressalva: a sequência de uma dada semente não é portável. Bibliotecas C diferentes usam geradores diferentes, então a semente 42 na glibc e a semente 42 no Windows dão números diferentes. Reprodutível em uma máquina, não entre máquinas.

Um jogo de dados

Tudo junto - semear uma vez, uma função auxiliar para o intervalo e um array contabilizando os resultados:

O histograma deve ter pico em 7 e afinar em direção a 2 e 12 - há seis maneiras de fazer 7 e uma maneira de fazer 2 ou 12. Um gerador que produzisse uma distribuição plana aqui estaria quebrado.

Duas páginas relacionadas: a biblioteca padrão mapeia o resto de <stdlib.h>, e funções matemáticas cobre <math.h>, que você vai querer assim que valores aleatórios alimentarem cálculos de verdade.

Perguntas frequentes

Como gerar um número aleatório em C?

Inclua <stdlib.h>, semeie uma vez no começo de main com srand((unsigned) time(NULL)) (que precisa de <time.h>) e então chame rand() para cada valor. rand() devolve um int entre 0 e RAND_MAX, inclusive.

Como obter um número aleatório entre dois valores em C?

Use rand() % (max - min + 1) + min. Para um lançamento de dado entre 1 e 6 isso é rand() % 6 + 1. O % n mapeia o resultado em 0..n-1 e somar min desloca a janela - só garanta que a contagem inclua os dois extremos, que é o que o + 1 faz.

Por que meu programa em C imprime os mesmos números aleatórios toda vez?

Porque você nunca chamou srand. Sem uma semente, rand() se comporta como se tivesse sido semeado com 1, então toda execução produz a sequência idêntica. Chame srand((unsigned) time(NULL)) uma vez no início do programa - uma vez, não antes de cada chamada de rand(), o que pioraria as coisas.

O que é viés do módulo na geração de números aleatórios?

rand() % n só é perfeitamente uniforme quando n divide RAND_MAX + 1 exatamente. Do contrário, os primeiros valores ocorrem uma vez a mais ao longo de toda a faixa, ficando levemente mais prováveis. Com RAND_MAX em 2147483647 e um n pequeno, o desvio fica muito abaixo do que um jogo ou uma simulação percebe, mas para criptografia ou estatística use um laço de rejeição ou um gerador apropriado.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR