Como gerar um número inteiro aleatório dentro de um intervalo

108

Esta é uma continuação de uma pergunta postada anteriormente:

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

Desejo ser capaz de gerar um número aleatório dentro de um determinado intervalo, como 1 a 6, para imitar os lados de um dado.

Como eu faria isso?

Jamie Keeling
fonte
3
se você olhar para a segunda resposta à pergunta a que se refere, terá a resposta. rand ()% 6.
Mats Fredriksson
2
Não entendi como funcionava, então decidi fazer uma pergunta separada para maior clareza.
Jamie Keeling
2
Pensamento aleatório: se você pesquisar uma seção cruzada aleatória de programadores, descobrirá que um número aleatório deles está pensando em maneiras de gerar números aleatoriamente. Considerando que o Universo é governado por leis precisas e previsíveis, não é interessante que tentemos gerar as coisas de forma mais aleatória? Perguntas como essa sempre tendem a trazer mais de 10 mil pôsteres.
Armstrongest
2
@Mats rand ()% 6 pode retornar um 0. Não é bom para um dado.
novo123456
Você pode marcar stackoverflow.com/a/6852396/419 como a resposta aceita em vez da resposta que leva a ela :) Obrigado.
Kev

Respostas:

173

Todas as respostas até agora estão matematicamente erradas. Retornar rand() % Nnão fornece uniformemente um número no intervalo, a [0, N)menos que Ndivida a duração do intervalo no qual rand()retorna (ou seja, é uma potência de 2). Além disso, não se tem idéia se os módulos de rand()são independentes: é possível que eles vão 0, 1, 2, ..., o que é uniforme, mas não muito aleatório. A única suposição que parece razoável fazer é que produz rand()uma distribuição de Poisson: quaisquer dois subintervalos não sobrepostos do mesmo tamanho são igualmente prováveis ​​e independentes. Para um conjunto finito de valores, isso implica uma distribuição uniforme e também garante que os valores de rand()sejam bem dispersos.

Isso significa que a única maneira correta de alterar o intervalo de rand()é dividi-lo em caixas; por exemplo, se RAND_MAX == 11você quiser um intervalo de 1..6, deve atribuir {0,1}a 1, {2,3}a 2 e assim por diante. Esses são intervalos separados e de tamanhos iguais e, portanto, são uniformemente e independentemente distribuídos.

A sugestão de usar a divisão de ponto flutuante é matematicamente plausível, mas apresenta problemas de arredondamento em princípio. Talvez doubleseja uma precisão alta o suficiente para fazê-lo funcionar; talvez não. Eu não sei e não quero ter que descobrir; em qualquer caso, a resposta depende do sistema.

A maneira correta é usar aritmética inteira. Ou seja, você deseja algo como o seguinte:

#include <stdlib.h> // For random(), RAND_MAX

// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
  unsigned long
    // max <= RAND_MAX < ULONG_MAX, so this is okay.
    num_bins = (unsigned long) max + 1,
    num_rand = (unsigned long) RAND_MAX + 1,
    bin_size = num_rand / num_bins,
    defect   = num_rand % num_bins;

  long x;
  do {
   x = random();
  }
  // This is carefully written not to overflow
  while (num_rand - defect <= (unsigned long)x);

  // Truncated division is intentional
  return x/bin_size;
}

O loop é necessário para obter uma distribuição perfeitamente uniforme. Por exemplo, se você receber números aleatórios de 0 a 2 e quiser apenas números de 0 a 1, continue puxando até não obter um 2; não é difícil verificar se isso dá 0 ou 1 com probabilidade igual. Esse método também é descrito no link que ns forneceu na resposta, embora codificado de forma diferente. Estou usando em random()vez de rand()porque tem uma distribuição melhor (conforme observado na página do manual para rand()).

Se você quiser obter valores aleatórios fora da faixa padrão [0, RAND_MAX], terá que fazer algo complicado. Talvez o mais expediente seja definir uma função random_extended()que extraia nbits (usando random_at_most()) e retorna [0, 2**n), e então aplicar random_at_most()com random_extended()no lugar de random()(e 2**n - 1no lugar de RAND_MAX) para extrair um valor aleatório menor que 2**n, supondo que você tenha um tipo numérico que pode conter tal um valor. Finalmente, é claro, você pode obter valores em [min, max]uso min + random_at_most(max - min), incluindo valores negativos.

Ryan Reich
fonte
1
@Adam Rosenfield, @ Ryan Reich: Em uma pergunta relacionada onde Adam respondeu: stackoverflow.com/questions/137783/… a resposta mais votada: O uso de 'módulo' seria incorreto, não? Para gerar 1..7 de 1..21, o procedimento descrito por Ryan deve ser usado. Corrija-me se eu estiver errado.
Arvind
1
Em uma análise posterior, outro problema aqui é que isso não funcionará quando max - min > RAND_MAX, o que é mais sério do que o problema que afirmei acima (por exemplo, o VC ++ tem RAND_MAXde apenas 32.767).
intervalo
2
O loop while poderia ser mais legível. Em vez de executar a atribuição na condicional, você provavelmente deseja a do {} while().
theJPster
4
Ei, esta resposta é citada pelo livro Comet OS;) Primeira vez que vejo isso em um livro de ensino
vpuente
3
Também é citado no livro OSTEP :) pages.cs.wisc.edu/~remzi/OSTEP (Capítulo 9, Página 4)
rafascar
33

Seguindo a resposta de @Ryan Reich, pensei em oferecer minha versão limpa. A primeira verificação de limites não é necessária devido à segunda verificação de limites, e a tornei iterativa em vez de recursiva. Ele retorna valores no intervalo [min, max], onde max >= mine 1+max-min < RAND_MAX.

unsigned int rand_interval(unsigned int min, unsigned int max)
{
    int r;
    const unsigned int range = 1 + max - min;
    const unsigned int buckets = RAND_MAX / range;
    const unsigned int limit = buckets * range;

    /* Create equal size buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    do
    {
        r = rand();
    } while (r >= limit);

    return min + (r / buckets);
}
theJPster
fonte
28
Observe que isso ficará preso em um loop infinito se intervalo> = RAND_MAX. Pergunte-me como eu sei: /
theJPster
24
Como você sabe!?
Fantástico Sr. Fox
1
Observe que você está comparando um int com um int sem sinal (r> = limite). O problema é facilmente resolvido criando limitum int (e opcionalmente buckettambém) desde RAND_MAX / range< INT_MAXe buckets * range<= RAND_MAX. EDITAR: Enviei e editei a proposta.
rrrrrrrrrrrrrrrrr
a solução de @Ryan Reich ainda me dá uma distribuição melhor (menos tendenciosa)
Vladimir
20

Esta é uma fórmula se você souber os valores máximos e mínimos de um intervalo e quiser gerar números inclusivos entre o intervalo:

r = (rand() % (max + 1 - min)) + min
Sattar
fonte
9
Conforme observado na resposta de Ryan, isso produz um resultado tendencioso.
David Wolever
6
Resultado tendencioso, potencial inttransbordamento com max+1-min.
chux - Reintegrar Monica em
1
isso funciona apenas com inteiros mínimo e máximo. Se o mínimo e o máximo estiverem flutuando, não será possível fazer a operação%
Taioli Francesco
17
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)rand()/RAND_MAX;

       return (max - min +1)*scaled + min;
}

Veja aqui outras opções.

nos
fonte
2
@ S.Lott - não realmente. Cada um distribui os casos de probabilidade ligeiramente mais alta de maneira diferente, só isso. A matemática dupla dá a impressão de que há mais precisão lá, mas você poderia facilmente usar (((max-min+1)*rand())/RAND_MAX)+mine obter provavelmente a mesma distribuição exata (assumindo que RAND_MAX é pequeno o suficiente em relação ao int para não estourar).
Steve314
4
Isso é um pouco perigoso: é possível que (muito raramente) retorne max + 1, se um rand() == RAND_MAXou outro rand()estiver muito próximo RAND_MAXe erros de ponto flutuante ultrapassem o resultado final max + 1. Por segurança, você deve verificar se o resultado está dentro da faixa antes de retorná-lo.
Mark Dickinson
1
@Christoph: Concordo RAND_MAX + 1.0. Ainda não tenho certeza se isso é bom o suficiente para evitar um max + 1retorno, no entanto: em particular, o + minno final envolve uma rodada que pode acabar produzindo max + 1grandes valores de rand (). Mais seguro abandonar totalmente essa abordagem e usar a aritmética de inteiros.
Mark Dickinson
3
Se RAND_MAXé substituída por RAND_MAX+1.0como Christoph sugere, então eu acredito que este é seguro, desde que o + miné feito usando inteiro aritmética: return (unsigned int)((max - min + 1) * scaled) + min. A razão (não óbvia) é que assumindo IEEE 754 aritmética e arredondamento meio para par, (e também isso max - min + 1é exatamente representável como um duplo, mas isso será verdade em uma máquina típica), é sempre verdade que x * scaled < xpara qualquer duplo positivo xe qualquer duplo scaledsatisfatório 0.0 <= scaled && scaled < 1.0.
Mark Dickinson
1
Falha para randr(0, UINT_MAX): sempre gera 0.
chux - Reintegrar Monica
12

Você não faria apenas:

srand(time(NULL));
int r = ( rand() % 6 ) + 1;

%é o operador de módulo. Essencialmente, ele vai apenas dividir por 6 e retornar o restante ... de 0 - 5

Armstrongest
fonte
1
Ele dará resultados de 1 a 6. É para isso que serve o +1.
Armstrongest
4
Simon, mostre-me um libc em uso em qualquer lugar que rand()inclua os bits de ordem inferior do estado do gerador (se ele usar um LCG). Eu não vi um até agora - todos eles (sim, incluindo MSVC com RAND_MAX sendo apenas 32767) removem os bits de ordem inferior. O uso de módulo não é recomendado por outras razões, nomeadamente porque distorce a distribuição a favor de números menores.
Joey
@Johannes: Então é seguro dizer que as caça-níqueis não usam módulo?
Armstrongest
Como eu excluiria um 0? Parece que se eu executá-lo em um loop de 30, talvez na segunda ou terceira vez que ele seja executado, haja um 0 aproximadamente na metade do caminho. Isso é algum tipo de sorte?
Jamie Keeling
@Johannes: Talvez não seja tanto um problema hoje em dia, mas tradicionalmente usar os bits de ordem inferior não é aconselhável. c-faq.com/lib/randrange.html
jamesdlin
9

Para aqueles que entendem o problema de polarização, mas não suportam o tempo de execução imprevisível de métodos baseados em rejeição, esta série produz um número inteiro aleatório progressivamente menos polarizado no [0, n-1]intervalo:

r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...

Ele faz isso sintetizando um número aleatório de i * log_2(RAND_MAX + 1)bits de ponto fixo de alta precisão (onde ié o número de iterações) e realizando uma longa multiplicação por n.

Quando o número de bits é suficientemente grande em comparação com n, a tendência torna-se incomensuravelmente pequena.

Não importa se RAND_MAX + 1é menor que n(como nesta questão ), ou se não é uma potência de dois, mas deve-se tomar cuidado para evitar estouro de inteiro se RAND_MAX * nfor grande.

sh1
fonte
2
RAND_MAXé frequentemente INT_MAX, então RAND_MAX + 1-> UB (como INT_MIN)
chux - Reintegrar Monica
@chux é o que quero dizer sobre "cuidado deve ser tomado para evitar estouro de inteiros se RAND_MAX * nfor grande". Você precisa organizar o uso de tipos apropriados para suas necessidades.
sh1
@chux " RAND_MAXgeralmente é INT_MAX" Sim, mas apenas em sistemas de 16 bits! Qualquer arquitetura razoavelmente moderna será colocada INT_MAXem 2 ^ 32/2 e RAND_MAXem 2 ^ 16 / 2. Esta é uma suposição incorreta?
cat
2
@cat Testado hoje 2 intcompiladores de 32 bits , encontrei RAND_MAX == 32767em um e RAND_MAX == 2147483647em outro. Minha experiência geral (décadas) é isso com RAND_MAX == INT_MAXmais frequência. Portanto, discorde que uma arquitetura razoavelmente moderna de 32 bits certamente terá um RAND_MAXat 2^16 / 2. Já que a especificação C permite 32767 <= RAND_MAX <= INT_MAX, eu codifico para isso de qualquer maneira, e não uma tendência.
chux - Reintegrar Monica
3
Ainda coberto por "cuidado deve ser tomado para evitar estouro de inteiros".
sh1 de
4

Para evitar o viés do módulo (sugerido em outras respostas), você sempre pode usar:

arc4random_uniform(MAX-MIN)+MIN

Onde "MAX" é o limite superior e "MIN" é o limite inferior. Por exemplo, para números entre 10 e 20:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

Solução simples e melhor do que usar "rand ()% N".

Magamig
fonte
1
Uau, isso é um bilhão de vezes melhor do que as outras respostas. Vale a pena notar que você precisa #include <bsd/stdlib.h>primeiro. Além disso, alguma ideia de como fazer isso no Windows sem MinGW ou CygWin?
gato
1
Não, por si só não é melhor do que as outras respostas, porque as outras respostas são mais genéricas. Aqui você está limitado a arc4random, as outras respostas permitem que você escolha uma fonte aleatória diferente, opere com diferentes tipos de números, ... e por último mas não menos importante, elas podem ajudar alguém a entender o problema. Não se esqueça que a questão também é interessante para outras pessoas que podem ter alguns requisitos especiais ou não ter acesso ao arc4random ... No entanto, se você tiver acesso a ele e quiser uma solução rápida, é de fato uma resposta muito boa 😊
K. Biermann
4

Aqui está um algoritmo ligeiramente mais simples do que a solução de Ryan Reich:

/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
    uint32_t range = (end - begin) + 1;
    uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);

    /* Imagine range-sized buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    uint32_t randVal = rand();
    while (randVal >= limit) randVal = rand();

    /// Return the position you hit in the bucket + begin as random number
    return (randVal % range) + begin;
}

Example (RAND_MAX := 16, begin := 2, end := 7)
    => range := 6  (1 + end - begin)
    => limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)

The limit is always a multiple of the range,
so we can split it into range-sized buckets:
    Possible-rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
    Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]

1st call to rand() => 13
     13 is not in the bucket-range anymore (>= limit), while-condition is true
         retry...
2nd call to rand() => 7
     7 is in the bucket-range (< limit), while-condition is false
         Get the corresponding bucket-value 1 (randVal % range) and add begin
    => 3
K. Biermann
fonte
1
RAND_MAX + 1pode facilmente transbordar intadição. Nesse caso, (RAND_MAX + 1) % rangegerará resultados questionáveis. Considere(RAND_MAX + (uint32_t)1)
chux - Reintegrar Monica em
2

Embora Ryan esteja correto, a solução pode ser muito mais simples com base no que se sabe sobre a origem da aleatoriedade. Para reafirmar o problema:

  • Existe uma fonte de aleatoriedade, produzindo números inteiros em uma faixa [0, MAX)com distribuição uniforme.
  • O objetivo é produzir números inteiros aleatórios uniformemente distribuídos no intervalo [rmin, rmax]onde 0 <= rmin < rmax < MAX.

Na minha experiência, se o número de caixas (ou "caixas") for significativamente menor do que o intervalo dos números originais, e a fonte original for criptograficamente forte - não há necessidade de passar por todo aquele rigamarole, e a divisão simples do módulo faria são suficientes (como output = rnd.next() % (rmax+1), se rmin == 0) e produzem números aleatórios que são distribuídos uniformemente "o suficiente" e sem qualquer perda de velocidade. O fator chave é a fonte de aleatoriedade (ou seja, crianças, não tente fazer isso em casa com rand()).

Aqui está um exemplo / prova de como funciona na prática. Eu queria gerar números aleatórios de 1 a 22, tendo uma fonte criptograficamente forte que produzisse bytes aleatórios (com base em Intel RDRAND). Os resultados são:

Rnd distribution test (22 boxes, numbers of entries in each box):     
 1: 409443    4.55%
 2: 408736    4.54%
 3: 408557    4.54%
 4: 409125    4.55%
 5: 408812    4.54%
 6: 409418    4.55%
 7: 408365    4.54%
 8: 407992    4.53%
 9: 409262    4.55%
10: 408112    4.53%
11: 409995    4.56%
12: 409810    4.55%
13: 409638    4.55%
14: 408905    4.54%
15: 408484    4.54%
16: 408211    4.54%
17: 409773    4.55%
18: 409597    4.55%
19: 409727    4.55%
20: 409062    4.55%
21: 409634    4.55%
22: 409342    4.55%   
total: 100.00%

Isso é o mais uniforme que preciso para o meu propósito (lançamento de dados justo, geração de livros de código criptograficamente fortes para máquinas de criptografia da Segunda Guerra Mundial, como http://users.telenet.be/d.rijmenants/en/kl-7sim.htm , etc. ) A saída não mostra qualquer tendência apreciável.

Aqui está a fonte do gerador de números aleatórios criptograficamente forte (verdadeiro): Intel Digital Random Number Generator e um código de amostra que produz números aleatórios de 64 bits (sem sinal).

int rdrand64_step(unsigned long long int *therand)
{
  unsigned long long int foo;
  int cf_error_status;

  asm("rdrand %%rax; \
        mov $1,%%edx; \
        cmovae %%rax,%%rdx; \
        mov %%edx,%1; \
        mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
        *therand = foo;
  return cf_error_status;
}

Compilei-o no Mac OS X com clang-6.0.1 (direto) e com gcc-4.8.3 usando o sinalizador "-Wa, q" (porque o GAS não suporta essas novas instruções).

Rato
fonte
Compilado com gcc randu.c -o randu -Wa,q(GCC 5.3.1 no Ubuntu 16) ou clang randu.c -o randu(Clang 3.8.0) funciona, mas descarta o núcleo em tempo de execução com Illegal instruction (core dumped). Alguma ideia?
cat
Primeiro, não sei se a sua CPU realmente suporta a instrução RDRAND. Seu sistema operacional é bastante recente, mas a CPU pode não ser. Segundo (mas isso é menos provável) - não tenho ideia de que tipo de montador o Ubuntu inclui (e o Ubuntu tende a ser pacotes de atualização relativamente ao contrário). Verifique o site da Intel que mencionei para saber como testar se sua CPU suporta RDRAND.
Mouse de
Você realmente tem bons pontos. O que ainda não consigo entender é o que há de tão errado rand(). Tentei alguns testes e postei essa pergunta, mas ainda não consigo encontrar uma resposta definitiva.
myradio de
1

Como dito antes, o módulo não é suficiente porque distorce a distribuição. Aqui está o meu código que mascara os bits e os usa para garantir que a distribuição não seja distorcida.

static uint32_t randomInRange(uint32_t a,uint32_t b) {
    uint32_t v;
    uint32_t range;
    uint32_t upper;
    uint32_t lower;
    uint32_t mask;

    if(a == b) {
        return a;
    }

    if(a > b) {
        upper = a;
        lower = b;
    } else {
        upper = b;
        lower = a; 
    }

    range = upper - lower;

    mask = 0;
    //XXX calculate range with log and mask? nah, too lazy :).
    while(1) {
        if(mask >= range) {
            break;
        }
        mask = (mask << 1) | 1;
    }


    while(1) {
        v = rand() & mask;
        if(v <= range) {
            return lower + v;
        }
    }

}

O código simples a seguir permite que você observe a distribuição:

int main() {

    unsigned long long int i;


    unsigned int n = 10;
    unsigned int numbers[n];


    for (i = 0; i < n; i++) {
        numbers[i] = 0;
    }

    for (i = 0 ; i < 10000000 ; i++){
        uint32_t rand = random_in_range(0,n - 1);
        if(rand >= n){
            printf("bug: rand out of range %u\n",(unsigned int)rand);
            return 1;
        }
        numbers[rand] += 1;
    }

    for(i = 0; i < n; i++) {
        printf("%u: %u\n",i,numbers[i]);
    }

}
Andrew Chambers
fonte
Torna-se bastante ineficiente quando você rejeita números de rand (). Isso será especialmente ineficiente quando o intervalo tiver um tamanho que pode ser escrito como 2 ^ k + 1. Então, quase metade de todas as suas tentativas de uma chamada rand () lenta será rejeitada pela condição. Seria melhor calcular o intervalo do módulo RAND_MAX. Tipo: v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;Eu entendo que o módulo é uma operação muito mais lenta do que o mascaramento, mas ainda acho ... que deve ser testado.
Øystein Schønning-Johansen,
rand()retorna um intno intervalo [0..RAND_MAX]. Esse intervalo pode facilmente ser um subintervalo de uint32_te randomInRange(0, ,b)nunca gera valores no intervalo (INT_MAX...b].
chux - Reintegrar Monica em
0

Retornará um número de ponto flutuante no intervalo [0,1]:

#define rand01() (((double)random())/((double)(RAND_MAX)))
Geremia
fonte