Gerando número inteiro aleatório a partir de um intervalo

157

Eu preciso de uma função que gere um número inteiro aleatório em determinado intervalo (incluindo valores de borda). Não tenho requisitos de qualidade / aleatoriedade irracionais, tenho quatro requisitos:

  • Eu preciso que seja rápido. Meu projeto precisa gerar milhões (ou às vezes dezenas de milhões) de números aleatórios e minha função atual de gerador provou ser um gargalo.
  • Eu preciso que ele seja razoavelmente uniforme (o uso de rand () é perfeitamente adequado).
  • os intervalos min-max podem ser de <0, 1> a <-32727, 32727>.
  • tem que ser semeada.

Atualmente, tenho o seguinte código C ++:

output = min + (rand() * (int)(max - min) / RAND_MAX)

O problema é que ele não é realmente uniforme - max é retornado apenas quando rand () = RAND_MAX (para Visual C ++ é 1/32727). Esse é um problema importante para pequenos intervalos como <-1, 1>, onde o último valor quase nunca é retornado.

Peguei caneta e papel e criei a seguinte fórmula (que se baseia no truque de arredondamento inteiro (int) (n + 0,5)):

insira a descrição da imagem aqui

Mas ainda não me dá uma distribuição uniforme. Execuções repetidas com 10000 amostras fornecem uma proporção de 37:50:13 para valores de valores -1, 0. 1.

Você poderia sugerir uma fórmula melhor? (ou até mesmo a função gerador de números pseudo-aleatórios)

Matěj Zábský
fonte
3
@ Bill MaGriff: sim. Tem o mesmo problema. Uma versão simplificada é: como você pode dividir 10 pedaços de doce entre três crianças uniformemente (sem quebrar nenhum dos doces)? A resposta é: você não pode - você deve dar três a cada criança e não dar o décimo a ninguém.
Jerry Coffin
5
Você já viu o Boost.Random ?
Fred Nurk
3
Verifique o artigo de Andrew Koenig "Um problema simples que quase nunca é resolvido corretamente": drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev
1
@Gene Bushuyev: Tanto Andrew quanto eu discutimos sobre esse assunto há um bom tempo. Consulte: groups.google.com/group/comp.lang.c++/browse_frm/thread/… e: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Coffin

Respostas:

105

Uma solução distribuída rápida, um pouco melhor que a sua, mas ainda não adequadamente uniforme, é

output = min + (rand() % static_cast<int>(max - min + 1))

Exceto quando o tamanho do intervalo é uma potência de 2, esse método produz números distribuídos não uniformes tendenciosos, independentemente da qualidade de rand(). Para um teste abrangente da qualidade deste método, leia isto .

Mark B
fonte
2
Obrigado, isso parece ser bom o suficiente para mim em testes rápidos - sua distribuição para -1, 0, 1 é quase 33:33:33.
Matěj Zábský 15/02
3
Retorna sempre o valor máximo. Estou perdendo aqui alguma coisa? : |
Rohan-patel
15
rand()deve ser considerado nocivo em C ++, existem maneiras muito melhores de obter algo distribuído uniformemente e realmente aleatório.
Mgetz
1
Realmente retorna um número correto dentro do intervalo 100% das vezes? Encontrei aqui outra resposta do stackoverflow que está usando a recursão para fazê-lo "da maneira certa": stackoverflow.com/a/6852396/623622 #
60625 Czarek Tomczak
2
Como é uma resposta altamente votada (que o desejada), que parece uma fonte confiável de informações para muitos leitores novos, acho muito importante mencionar a qualidade e os perigos potenciais dessa solução, então fiz uma edição.
plasmacel
296

A resposta mais simples (e, portanto, a melhor) em C ++ (usando o padrão de 2011) é

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

Não há necessidade de reinventar a roda. Não precisa se preocupar com preconceitos. Não precisa se preocupar em usar o tempo como semente aleatória.

Walter
fonte
1
Hoje em dia, essa deve ser a resposta . Referência de geração de números pseudo-aleatórios para mais recursos.
alextoind
8
Concordo com o "mais simples" (e o mais idiomático), não com o "melhor". Infelizmente, o padrão não oferece garantia random_device, o que pode ser completamente quebrado em alguns casos . Além disso, mt19937apesar de ser uma escolha de uso geral muito boa, não é o mais rápido dos geradores de boa qualidade (veja esta comparação ) e, portanto, pode não ser o candidato ideal para o OP.
Alberto M
1
@AlbertoM Infelizmente, a comparação a que você se refere não fornece detalhes suficientes e não é reproduzível, o que a torna duvidosa (além disso, é de 2015, enquanto minha resposta remonta a 2013). Pode muito bem ser verdade que existem métodos melhores (e esperamos que no futuro minstdseja esse método), mas isso é progresso. Quanto à má implementação do random_device- isso é horrível e deve ser considerado um bug (possivelmente também do padrão C ++, se permitir).
187 Walter Walter
1
Eu concordo totalmente com você; Eu realmente não quero criticar a sua solução de per se , só queria avisar o leitor casual que a resposta definitiva sobre o assunto, apesar das promessas de C ++ 11 ainda está para ser escrito. Vou publicar uma visão geral do assunto a partir de 2015 como resposta a uma pergunta relacionada .
Alberto M
1
Isso é "mais simples"? Você poderia explicar por que o claramente muito mais simples rand()não é uma opção e isso importa para o uso não crítico, como gerar um índice dinâmico aleatório? Além disso, eu tenho que me preocupar em construir random_device/ mt19937/ uniform_int_distributionem um loop apertado / função embutida? Eu prefiro preferir passá-los por aí?
bluenote10
60

Se o seu compilador suportar C ++ 0x e usá-lo for uma opção para você, <random>é provável que o novo cabeçalho padrão atenda às suas necessidades. Possui alta qualidade, uniform_int_distributionque aceita limites mínimos e máximos (inclusive conforme necessário), e você pode escolher entre vários geradores de números aleatórios para conectar-se a essa distribuição.

Aqui está o código que gera um milhão de aleatórios intdistribuídos uniformemente em [-57, 365]. Usei as novas <chrono>instalações std para cronometrar, pois você mencionou que o desempenho é uma grande preocupação para você.

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

Para mim (Intel Core i5 de 2,8 GHz), é impresso:

2.10268e + 07 números aleatórios por segundo.

Você pode propagar o gerador passando um int para seu construtor:

    G g(seed);

Se, posteriormente, você descobrir que intnão cobre o intervalo necessário para sua distribuição, isso pode ser remediado alterando-se o seguinte uniform_int_distribution(por exemplo, para long long):

    typedef std::uniform_int_distribution<long long> D;

Se você descobrir mais tarde que o minstd_randgerador não é de alta qualidade, isso também pode ser facilmente trocado. Por exemplo:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

Ter controle separado sobre o gerador de números aleatórios e a distribuição aleatória pode ser bastante libertadora.

Também calculei (não mostrado) os 4 primeiros "momentos" desta distribuição (usando minstd_rand) e os comparei aos valores teóricos na tentativa de quantificar a qualidade da distribuição:

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

(O x_prefixo refere-se a "esperado")

Howard Hinnant
fonte
3
Esta resposta pode usar um pequeno trecho de código de resumo que mostra apenas o código realmente necessário para gerar um número inteiro aleatório a partir de um intervalo.
arekolek
O problema é facilitado pelo fato de que min e max da distribuição nunca mudam. E se você tivesse que criar da cada iteração com limites diferentes? Quanto isso desaceleraria o ciclo?
quant_dev
15

Vamos dividir o problema em duas partes:

  • Gere um número aleatório nno intervalo de 0 a (max-min).
  • Adicione min a esse número

A primeira parte é obviamente a mais difícil. Vamos supor que o valor de retorno de rand () seja perfeitamente uniforme. Usar o módulo adicionará um viés aos primeiros (RAND_MAX + 1) % (max-min+1)números. Portanto, se pudéssemos mudar magicamente RAND_MAXpara RAND_MAX - (RAND_MAX + 1) % (max-min+1), não haveria mais preconceito.

Acontece que podemos usar essa intuição se estivermos dispostos a permitir o pseudo-não-determinismo no tempo de execução do nosso algoritmo. Sempre que rand () retorna um número muito grande, simplesmente pedimos outro número aleatório até obtermos um número suficientemente pequeno.

O tempo de execução agora está distribuído geometricamente , com valor esperado, 1/ponde pestá a probabilidade de obter um número pequeno o suficiente na primeira tentativa. Como RAND_MAX - (RAND_MAX + 1) % (max-min+1)sempre é menor que (RAND_MAX + 1) / 2, sabemos que p > 1/2, portanto, o número esperado de iterações sempre será menor que duas para qualquer intervalo. Deve ser possível gerar dezenas de milhões de números aleatórios em menos de um segundo em uma CPU padrão com esta técnica.

EDITAR:

Embora o acima exposto seja tecnicamente correto, a resposta do DSimon é provavelmente mais útil na prática. Você não deve implementar essas coisas sozinho. Eu já vi muitas implementações de amostragem por rejeição e geralmente é muito difícil ver se está correto ou não.

Jørgen Fogh
fonte
Para completar: Esta é a amostra de rejeição .
etarion
3
Curiosidade: Joel Spolsky certa vez mencionou uma versão desta pergunta como um exemplo do que o StackOverflow era bom em responder. Eu olhei através das respostas sobre o site que envolve a rejeição de amostragem naquela época e cada único um estava incorreta.
Jørgen Fogh
13

Que tal o Mersenne Twister ? A implementação do impulso é bastante fácil de usar e é bem testada em muitos aplicativos do mundo real. Eu mesmo o usei em vários projetos acadêmicos, como inteligência artificial e algoritmos evolutivos.

Aqui está o exemplo deles, onde eles fazem uma função simples para rolar um dado de seis lados:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

Ah, e aqui está um pouco mais deste cafetão, caso você não esteja convencido de que deve usá-lo sobre o amplamente inferior rand():

O Mersenne Twister é um gerador de "número aleatório" inventado por Makoto Matsumoto e Takuji Nishimura; seu site inclui inúmeras implementações do algoritmo.

Essencialmente, o Mersenne Twister é um registro de deslocamento de feedback linear muito grande. O algoritmo opera em uma semente de 19.937 bits, armazenada em uma matriz de 624 elementos de números inteiros não assinados de 32 bits. O valor 2 ^ 19937-1 é um primo de Mersenne; a técnica para manipular a semente é baseada em um algoritmo "torcido" mais antigo - daí o nome "Mersenne Twister".

Um aspecto atraente do Mersenne Twister é o uso de operações binárias - em oposição à multiplicação demorada - para gerar números. O algoritmo também tem um período muito longo e boa granularidade. É rápido e eficaz para aplicações não criptográficas.

Aphex
fonte
1
O twister de Mersenne é um bom gerador, mas o problema com o qual ele permanece permanece, independentemente do gerador subjacente.
Jerry Coffin
Eu não quero usar o Boost apenas para o gerador aleatório, porque (como meu projeto é uma biblioteca), isso significa introduzir outra dependência no projeto. Provavelmente serei forçado a usá-lo de qualquer maneira no futuro, para que eu possa mudar para este gerador.
Matěj Zábský 15/02
1
Caixão @ Jerry Qual problema? Eu o ofereci porque atendia a todos os requisitos dele: é rápido, é uniforme (usando a boost::uniform_intdistribuição), você pode transformar os intervalos mín. Máx. Em qualquer coisa que desejar e é fácil de cultivar.
Aphex
@mzabsky Eu provavelmente não deixaria isso me parar, quando tive que enviar meus projetos aos meus professores para apresentação, apenas incluí os arquivos relevantes do cabeçalho de impulso que eu estava usando; você não precisa empacotar toda a biblioteca de aumento de 40 MB com o seu código. É claro que, no seu caso, isso pode não ser viável por outros motivos, como direitos autorais ...
Aphex 15/02/11
@Aphex Meu projeto não é realmente um simulador científico ou algo que precise de uma distribuição realmente uniforme. Eu usei o gerador antigo por 1,5 anos sem nenhum problema, só notei a distribuição tendenciosa quando precisei dele para gerar números a partir de um intervalo muito pequeno (3 neste caso). A velocidade ainda é um argumento para considerar a solução de impulso. Examinarei sua licença para ver se posso adicionar os poucos arquivos necessários ao meu projeto - gosto do "Checkout -> F5 -> pronto para usar", como está agora.
21811 Mathj Zábský
11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Este é um mapeamento de 32768 inteiros para (nMax-nMin + 1) inteiros. O mapeamento será bastante bom se (nMax-nMin + 1) for pequeno (como no seu requisito). Observe, porém, que se (nMax-nMin + 1) for grande, o mapeamento não funcionará (por exemplo - você não pode mapear valores 32768 para 30000 valores com probabilidade igual). Se esses intervalos forem necessários - você deve usar uma fonte aleatória de 32 ou 64 bits, em vez dos rand de 15 bits () ou ignorar os resultados de rand () que estão fora do intervalo.

Lior Kogan
fonte
Apesar de sua impopularidade, é também isso que uso nos meus projetos não científicos. Fácil de entender (você não precisa de um diploma de matemática) e tem um desempenho adequado (nunca teve que criar um perfil para nenhum código). :) Em caso de grandes intervalos, eu acho que nós poderíamos seqüência de dois rand () valoriza juntos e obter um valor de 30 bits para trabalhar com (assumindo RAND_MAX = 0x7fff, ou seja, 15 bits aleatórios)
efotinis
mude RAND_MAXpara (double) RAND_MAXpara evitar um aviso de estouro inteiro.
alex
4

Aqui está uma versão imparcial que gera números em [low, high]:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

Se seu intervalo for razoavelmente pequeno, não há motivo para armazenar em cache o lado direito da comparação no doloop.

Jeremiah Willcock
fonte
Na IMO, nenhuma das soluções apresentadas há realmente muita melhoria. Sua solução baseada em loop funciona, mas provavelmente é bastante ineficiente, especialmente para um pequeno intervalo como o OP discute. Sua solução de desvio uniforme não produz realmente desvios uniformes . No máximo, camufla a falta de uniformidade.
Jerry Coffin
@ Jerry: Por favor, verifique a nova versão.
Jeremiah Willcock
Estou um pouco incerto sobre isso funcionando corretamente. Pode parecer, mas a correção não parece óbvia, pelo menos para mim.
Jerry Coffin
@ Jerry: Aqui está o meu raciocínio: suponha que o alcance seja [0, h)simples. A chamada rand()tem RAND_MAX + 1possíveis valores de retorno; levando rand() % hrecolhimentos (RAND_MAX + 1) / hdeles para cada um dos hvalores de saída, exceto que (RAND_MAX + 1) / h + 1eles são mapeados para valores inferiores a (RAND_MAX + 1) % h(por causa do último ciclo parcial através das hsaídas). Portanto, removemos os (RAND_MAX + 1) % hpossíveis resultados para obter uma distribuição imparcial.
Jeremiah Willcock
3

Eu recomendo a biblioteca Boost.Random , é super detalhada e bem documentada, permite especificar explicitamente qual distribuição você deseja e, em cenários não criptográficos, pode realmente superar a implementação típica de uma biblioteca C.

DSimon
fonte
1

suponha que min e max são valores int, [e] significa incluir esse valor, (e) significa não incluir esse valor, usando acima para obter o valor correto usando c ++ rand ()

referência: para () [] definir, visite:

https://en.wikipedia.org/wiki/Interval_(mathematics)

para a função rand e srand ou RAND_MAX define, visite:

http://en.cppreference.com/w/cpp/numeric/random/rand

[mínimo máximo]

int randNum = rand() % (max - min + 1) + min

(mínimo máximo]

int randNum = rand() % (max - min) + min + 1

[mínimo máximo)

int randNum = rand() % (max - min) + min

(mínimo máximo)

int randNum = rand() % (max - min - 1) + min + 1
Huang Kun
fonte
0

Neste tópico, a amostragem de rejeição já foi discutida, mas eu queria sugerir uma otimização com base no fato de que rand() % 2^somethingnão introduz viés, como já mencionado acima.

O algoritmo é realmente simples:

  • calcular a menor potência de 2 maior que a duração do intervalo
  • aleatoriamente um número nesse intervalo "novo"
  • retorne esse número se for menor que o comprimento do intervalo original
    • rejeitar de outra forma

Aqui está o meu código de exemplo:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

Isso funciona bem especialmente para pequenos intervalos, porque a potência de 2 será "mais próxima" da duração real do intervalo e, portanto, o número de erros será menor.

PS
Obviamente, evitar a recursão seria mais eficiente (não é necessário calcular repetidamente o teto do log ..), mas achei que era mais legível para este exemplo.

Pado
fonte
0

Observe que, na maioria das sugestões, o valor aleatório inicial obtido da função rand (), que normalmente é de 0 a RAND_MAX, é simplesmente desperdiçado. Você está criando apenas um número aleatório, enquanto existe um procedimento sólido que pode lhe dar mais.

Suponha que você deseja a região [min, max] de números aleatórios inteiros. Começamos de [0, max-min]

Pegue a base b = max-min + 1

Comece representando um número obtido de rand () na base b.

Dessa forma, você obtém o piso (log (b, RAND_MAX)) porque cada dígito na base b, exceto possivelmente o último, representa um número aleatório no intervalo [0, max-min].

É claro que o deslocamento final para [min, max] é simples para cada número aleatório r + min.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

Se NUM_DIGIT é o número de dígitos na base b que você pode extrair e que é

NUM_DIGIT = floor(log(b,RAND_MAX))

o exposto acima é uma implementação simples de extração de números aleatórios NUM_DIGIT de 0 a b-1 de um número aleatório RAND_MAX que fornece b <RAND_MAX.

alex.peter
fonte
-1

A fórmula para isso é muito simples, então tente esta expressão,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0
Sohail xIN3N
fonte
2
Todo o problema estava usando o rand de C / C ++, que retorna inteiro em um intervalo especificado pelo tempo de execução. Conforme demonstrado neste encadeamento, o mapeamento de números inteiros aleatórios de [0, RAND_MAX] para [MIN, MAX] não é totalmente direto, se você deseja evitar a destruição de suas propriedades estatísticas ou desempenho. Se você tiver dobra no intervalo [0, 1], o mapeamento é fácil.
Matěj Zábský
2
Sua resposta está errada, você deve usar o módulo:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes
-2

A seguinte expressão deve ser imparcial se não me engano:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

Estou assumindo aqui que rand () fornece um valor aleatório no intervalo entre 0,0 e 1,0, NÃO incluindo 1,0 e que max e min são números inteiros com a condição que min <max.

Moritz
fonte
std::floorretorna doublee precisamos de um valor inteiro aqui. Gostaria apenas de transmitir, em intvez de usar std::floor.
Musiphil