Gere números aleatórios uniformemente em todo um intervalo


93

Eu preciso gerar números aleatórios dentro de um intervalo especificado, [max; min].

Além disso, os números aleatórios devem ser distribuídos uniformemente ao longo do intervalo, não localizados em um ponto específico.

Atualmente estou gerando como:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

Em meus testes, números aleatórios são gerados em torno de apenas um ponto.

Example
min = 3604607;
max = 7654607;

Números aleatórios gerados:

3631594
3609293
3630000
3628441
3636376
3621404

Das respostas abaixo: OK, RAND_MAX é 32767. Estou na plataforma C ++ Windows. Existe algum outro método para gerar números aleatórios com uma distribuição uniforme?


2
Construa um Dice-O-Matic: gamesbyemail.com/News/DiceOMatic
Jarett Millard

1
Eu não fazia ideia de que o C ++ rand()era uniforme. Qual biblioteca você está usando? cstdlib.h's rand()não é uniforme: cplusplus.com/reference/cstdlib/rand
Mike Warren

3
Não, rand () é uniforme (exceto em algumas implementações com erros iniciais). o que não é uniforme é usar o operador de módulo '%' para restringir o intervalo. Consulte stackoverflow.com/questions/2999075/… para uma solução adequada, ou se você tiver 'arc4random_uniform' disponível, você também pode usá-lo diretamente.
John Meacham,

@ Alien01: Você consideraria mudar a resposta aceita para "Sapato" ("Por que rand é uma má ideia" etc.)? Minha resposta está realmente desatualizada e toda vez que recebo um voto positivo, sinto como se alguém estivesse correndo pelo corredor errado.
peterchen

Bom artigo sobre aleatório em c ++ 11.
Pupsik

Respostas:


153

Porque randé uma má ideia

A maioria das respostas que você obteve aqui faz uso da randfunção e do operador de módulo. Esse método pode não gerar números uniformemente (depende do intervalo e do valor de RAND_MAX) e, portanto, é desencorajado.

C ++ 11 e geração em uma faixa

Com o C ++ 11, várias outras opções surgiram. Uma delas se adapta às suas necessidades, para gerar um número aleatório em um intervalo, muito bem: std::uniform_int_distribution. Aqui está um exemplo:

const int range_from  = 0;
const int range_to    = 10;
std::random_device                  rand_dev;
std::mt19937                        generator(rand_dev());
std::uniform_int_distribution<int>  distr(range_from, range_to);

std::cout << distr(generator) << '\n';

E aqui está o exemplo de execução.

Outros geradores aleatórios

O <random>cabeçalho oferece inúmeros outros geradores de números aleatórios com diferentes tipos de distribuições, incluindo Bernoulli, Poisson e normal.

Como posso embaralhar um contêiner?

O padrão fornece std::shuffle, que pode ser usado da seguinte forma:

std::vector<int> vec = {4, 8, 15, 16, 23, 42};

std::random_device random_dev;
std::mt19937       generator(random_dev());

std::shuffle(vec.begin(), vec.end(), generator);

O algoritmo irá reordenar os elementos de forma aleatória, com uma complexidade linear.

Boost.Random

Outra alternativa, caso você não tenha acesso a um compilador C ++ 11 +, é usar Boost.Random . Sua interface é muito semelhante à do C ++ 11.


22
ATENÇÃO a esta resposta, por ser bem mais moderna.
gsamaras

Esta é a resposta certa. Obrigado! Ainda assim, gostaria de ver uma descrição mais detalhada de cada etapa desse código. Por exemplo, o que é um mt19937tipo?
Apollo

@Apollo A documentação diz "Mersenne Twister de 32 bits por Matsumoto e Nishimura, 1998". Presumo que seja um algoritmo para gerar números pseudo-aleatórios.
Sapato

@Shoe, para um determinado intervalo, gera números na mesma ordem 1 9 6 2 8 7 1 4 7 7,. Você sabe como randomizar isso toda vez que rodamos o programa?

1
@Richard Qual é a alternativa?
Sapato

59

[editar] Aviso: Não use rand()para estatísticas, simulação, criptografia ou qualquer coisa séria.

É bom o suficiente para fazer os números parecerem aleatórios para um humano típico com pressa, nada mais.

Veja a resposta de @Jfefrey para melhores opções, ou esta resposta para números aleatórios cripto-seguros.


Geralmente, os bits altos mostram uma distribuição melhor do que os bits baixos, portanto, a maneira recomendada de gerar números aleatórios de um intervalo para fins simples é:

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Nota : certifique-se de que RAND_MAX + 1 não transborde (obrigado Demi)!

A divisão gera um número aleatório no intervalo [0, 1); "estique" isso para o intervalo necessário. Somente quando max-min + 1 se aproxima de RAND_MAX você precisa de uma função "BigRand ()" como postada por Mark Ransom.

Isso também evita alguns problemas de fatiamento devido ao módulo, o que pode piorar ainda mais seus números.


Não é garantido que o gerador de números aleatórios embutido tenha a qualidade necessária para simulações estatísticas. É normal que os números "pareçam aleatórios" para um humano, mas para uma aplicação séria, você deve pegar algo melhor - ou pelo menos verificar suas propriedades (distribuição uniforme geralmente é boa, mas os valores tendem a se correlacionar, e a sequência é determinística ) Knuth tem um tratado excelente (embora difícil de ler) sobre geradores de números aleatórios, e recentemente descobri que o LFSR é excelente e muito simples de implementar, já que suas propriedades são adequadas para você.


4
O BigRand pode fornecer melhores resultados, mesmo quando o intervalo desejado não excede RAND_MAX. Considere quando RAND_MAX é 32767 e você deseja 32767 valores possíveis - dois desses 32768 números aleatórios (incluindo zero) serão mapeados para a mesma saída e terão duas vezes mais probabilidade de ocorrer do que os outros. Dificilmente uma propriedade aleatória ideal!
Mark Ransom

7
(RAND_MAX + 1) é uma má ideia. Isso pode rolar e dar a você um valor negativo. Melhor fazer algo como: ((duplo) RAND_MAX) + 1.0
Demi

3
@peterchen: Acho que você entendeu mal o que demi estava dizendo. Ela quis dizer isso: ( rand() / ((double)RAND_MAX+1)) * (max-min+1) + min simplesmente mova a conversão para o dobro e evite o problema.
Mooing Duck

3
Além disso, isso meramente altera a distribuição dos valores inferiores de 32.767 no intervalo para valores de 32.767 uniformemente distribuídos no intervalo, e os 4017233 valores restantes nunca serão selecionados por este algoritmo.
Mooing Duck

1
A resposta dada está errada por 1. A equação correta é: ((double) rand () / (RAND_MAX + 1.0)) * (max-min) + min O "max-min + 1" é usado ao usar% not * . Você verá por que quando fizer min = 0, max = 1. Pode peterchen ou @peter-mortensen alterá-lo.
davepc

17

Gostaria de complementar as excelentes respostas de Angry Shoe e Peterchen com uma breve visão geral do estado da arte em 2015:

Algumas boas escolhas

randutils

A randutilsbiblioteca (apresentação) é uma novidade interessante, oferecendo uma interface simples e recursos aleatórios (declarados) robustos. Tem a desvantagem de adicionar uma dependência ao seu projeto e, sendo novo, não foi exaustivamente testado. De qualquer forma, por ser gratuito (licença MIT) e somente cabeçalho, acho que vale a pena tentar.

Amostra mínima: um lançamento de dados

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

Mesmo se alguém não estiver interessado na biblioteca, o site ( http://www.pcg-random.org/ ) oferece muitos artigos interessantes sobre o tema da geração de números aleatórios em geral e a biblioteca C ++ em particular.

Boost.Random

Boost.Random (documentação) é a biblioteca que inspirou C++11's <random>, com quem compartilha muito da interface. Embora teoricamente também seja uma dependência externa, Boostagora tem um status de biblioteca "quase padrão" e seu Randommódulo pode ser considerado a escolha clássica para geração de números aleatórios de boa qualidade. Apresenta duas vantagens em relação à C++11solução:

  • é mais portátil, precisando apenas de suporte de compilador para C ++ 03
  • seus random_devicemétodos específicos de sistema usa para oferta de semeadura de boa qualidade

A única pequena falha é que a oferta do módulo random_devicenão é apenas o cabeçalho, é necessário compilar e vincular boost_random.

Amostra mínima: um lançamento de dados

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Embora a amostra mínima funcione bem, os programas reais devem usar um par de melhorias:

  • faça mt19937umthread_local : o gerador é bastante gordo (> 2 KB) e é melhor não alocado na pilha
  • semente mt19937com mais de um inteiro: o Mersenne Twister tem um grande estado e pode se beneficiar de mais entropia durante a inicialização

Algumas escolhas não tão boas

A biblioteca C ++ 11

Embora seja a solução mais idiomática, a <random>biblioteca não oferece muito em troca da complexidade de sua interface, mesmo para as necessidades básicas. A falha está em std::random_device: o padrão não exige nenhuma qualidade mínima para sua produção (desde que entropy()retorne 0) e, a partir de 2015, MinGW (não o compilador mais usado, mas dificilmente uma escolha esotérica) sempre imprimirá 4na amostra mínima.

Amostra mínima: um lançamento de dados

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

Se a implementação não estiver podre, esta solução deve ser equivalente à do Boost, e as mesmas sugestões se aplicam.

Solução de Godot

Amostra mínima: um lançamento de dados

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

Esta é uma solução simples, eficaz e organizada. Só defeito, vai demorar um pouco para compilar - cerca de dois anos, desde que o C ++ 17 seja lançado no prazo e a randintfunção experimental seja aprovada no novo Padrão. Talvez nessa altura também as garantias sobre a qualidade da sementeira melhorem.

A solução pior é melhor

Amostra mínima: um lançamento de dados

#include <cstdlib>
#include <ctime>
#include <iostream>

int main() {
    std::srand(std::time(nullptr));
    std::cout << (std::rand() % 6 + 1);
}

A antiga solução C é considerada prejudicial, e por boas razões (veja as outras respostas aqui ou esta análise detalhada ). Ainda assim, tem suas vantagens: é simples, portátil, rápido e honesto, no sentido de que se sabe que os números aleatórios que se obtém dificilmente são decentes e, portanto, não se fica tentado a usá-los para fins sérios.

A solução de troll de contabilidade

Amostra mínima: um lançamento de dados

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

Embora 9 seja um resultado um tanto incomum para um lançamento de dados regular, deve-se admirar a excelente combinação de boas qualidades nesta solução, que consegue ser a mais rápida, simples, amigável de cache e mais portátil. Ao substituir 9 por 4, obtém-se um gerador perfeito para qualquer tipo de dado de Dungeons and Dragons, enquanto ainda evita os valores carregados de símbolos 1, 2 e 3. A única pequena falha é que, por causa do mau humor dos trolls contadores de Dilbert, este programa realmente gera um comportamento indefinido.


A randutilsbiblioteca agora se chama PCG.
tay10r

11

Se RAND_MAXfor 32767, você pode dobrar o número de bits facilmente.

int BigRand()
{
    assert(INT_MAX/(RAND_MAX+1) > RAND_MAX);
    return rand() * (RAND_MAX+1) + rand();
}

Eu não acho que isso funcione. Geradores de números pseudoaleatórios são tipicamente determinísticos. Por exemplo, se a primeira randchamada retornar 0x1234e a segunda 0x5678, você receberá 0x12345678. Esse é o único número que você pode começar com 0x1234, porque o próximo número sempre será 0x5678. Você obtém resultados de 32 bits, mas tem apenas 32.768 números possíveis.
user694733 de

@ user694733 um bom gerador de número aleatório tem um período maior do que o número de saídas que pode gerar, então 0x1234 nem sempre será seguido por 0x5678.
Mark Ransom de

9

Se você puder, use Boost . Tive sorte com sua biblioteca aleatória .

uniform_int deve fazer o que quiser.


Fiz alguns trabalhos em uniform_int com um twister merseinne e, infelizmente, para certos intervalos, os valores retornados por uniform_int não são tão uniformes quanto eu esperava. Por exemplo uniform_int <> (0, 3) tende a produzir mais 0's do que 1's ou 2's
ScaryAardvark

@ScaryAardvark isso soa como uma implementação ruim de uniform_intentão. É muito fácil gerar uma saída imparcial; várias questões aqui demonstram o método.
Mark Ransom de

@Mark Ransom. Sim, eu concordo inteiramente.
ScaryAardvark

8

Se você está preocupado com a aleatoriedade e não com a velocidade, deve usar um método seguro de geração de números aleatórios. Existem várias maneiras de fazer isso ... A mais fácil é usar o Gerador de números aleatórios do OpenSSL .

Você também pode escrever seu próprio usando um algoritmo de criptografia (como AES ). Escolhendo uma semente e um IV e, em seguida, criptografando novamente a saída da função de criptografia continuamente. Usar o OpenSSL é mais fácil, mas menos varonil.


Não posso usar nenhuma biblioteca de terceiros? Estou restrito apenas a C ++.
Anand

Em seguida, siga o caminho principal, implemente AES ou algum outro algoritmo de criptografia.
SoapBox

2
O RC4 é trivial para codificar e aleatório o suficiente para todos os propósitos práticos (exceto WEP, mas isso não é totalmente culpa do RC4). Quero dizer, é um código incrivelmente trivial. Tipo, 20 linhas ou mais. A entrada da Wikipedia tem pseudo-código.
Steve Jessop

4
Por que você não pode usar um código de terceiros? Se esta é uma questão de dever de casa, você deve dizer isso, porque muitas pessoas preferem dar dicas úteis em vez de fornecer soluções completas neste caso. Se não for um dever de casa, vá chutar o cara que diz "nenhum código de terceiros", porque ele é um idiota.
DevSolar

Ligação mais directa com os documentos de função OpenSSL rand (): openssl.org/docs/crypto/rand.html#
DevSolar

5

Você deve olhar RAND_MAXpara o seu compilador / ambiente específico. Acho que você veria esses resultados se estivesse rand()produzindo um número aleatório de 16 bits. (você parece estar assumindo que será um número de 32 bits).

Não posso prometer que essa é a resposta, mas por favor poste seu valor de RAND_MAXe um pouco mais de detalhes sobre seu ambiente.


3

Verificar o que RAND_MAX está em seu sistema - suponho que sejam apenas 16 bits e seu alcance é muito grande para isso.

Além disso, consulte esta discussão sobre: ​​Como gerar números inteiros aleatórios em um intervalo desejado e as notas sobre o uso (ou não) da função C rand () .


Ok RAND_MAX é 32767. Estou na plataforma Windows C ++ .. Existe algum outro método para gerar números aleatórios com distribuição uniforme?
Anand

2

Este não é o código, mas essa lógica pode ajudá-lo.

static double rnd(void)
{
   return (1.0 / (RAND_MAX + 1.0) * ((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
    register int i;
    srand( seed );
    for( i = 0; i < POOLSIZE; i++){
        pool[i] = rnd();
    }
}

 // This function returns a number between 0 and 1
 static double rnd0_1(void)
 {
    static int i = POOLSIZE-1;
    double r;

    i = (int)(POOLSIZE*pool[i]);
    r = pool[i];
    pool[i] = rnd();
    return (r);
}

2

Se quiser que os números sejam distribuídos uniformemente ao longo do intervalo, você deve dividir o intervalo em várias seções iguais que representam o número de pontos de que você precisa. Em seguida, obtenha um número aleatório com um mínimo / máximo para cada seção.

Como outra observação, você provavelmente não deve usar, rand()pois não é muito bom para gerar números aleatórios. Não sei em que plataforma você está executando, mas provavelmente há uma função melhor que você pode chamar random().


1

Isso deve fornecer uma distribuição uniforme ao longo do intervalo [low, high)sem usar flutuadores, desde que o intervalo geral seja menor que RAND_MAX.

uint32_t rand_range_low(uint32_t low, uint32_t high)
{
    uint32_t val;
    // only for 0 < range <= RAND_MAX
    assert(low < high);
    assert(high - low <= RAND_MAX);

    uint32_t range = high-low;
    uint32_t scale = RAND_MAX/range;
    do {
        val = rand();
    } while (val >= scale * range); // since scale is truncated, pick a new val until it's lower than scale*range
    return val/scale + low;
}

e para valores maiores que RAND_MAX você quer algo como

uint32_t rand_range(uint32_t low, uint32_t high)
{
    assert(high>low);
    uint32_t val;
    uint32_t range = high-low;
    if (range < RAND_MAX)
        return rand_range_low(low, high);
    uint32_t scale = range/RAND_MAX;
    do {
        val = rand() + rand_range(0, scale) * RAND_MAX; // scale the initial range in RAND_MAX steps, then add an offset to get a uniform interval
    } while (val >= range);
    return val + low;
}

É basicamente assim que std :: uniform_int_distribution faz as coisas.


0

Por sua natureza, uma pequena amostra de números aleatórios não precisa ser distribuída uniformemente. Afinal, eles são aleatórios. Concordo que, se um gerador de números aleatórios está gerando números que parecem estar agrupados de maneira consistente, provavelmente há algo errado com ele.

Mas tenha em mente que a aleatoriedade não é necessariamente uniforme.

Edit: Eu adicionei "pequena amostra" para esclarecer.


"uniformemente distribuído" tem um significado bem definido e os geradores aleatórios padrão geralmente chegam perto.
Peterchen

Sim, você está certo, geradores de números aleatórios devem produzir uma saída que com o tempo, geralmente é uniforme em sua distribuição. Acho que meu ponto é que em um pequeno número de instâncias (6 como mostrado no exemplo) a saída nem sempre será uniforme.
Kluge

Kluge está certo. A distribuição uniforme em uma pequena amostra indica que a amostra definitivamente não é aleatória.
Bill the Lizard

1
Bill, isso não indica tal coisa. Amostras pequenas geralmente não têm sentido, mas se o RNG é suposto ser uniforme e a saída é uniforme, por que isso é pior do que uma pequena amostra não uniforme?
Dan Dyer

2
Distribuições significativas de qualquer maneira indicam não aleatoriedade: acho que Bill apenas significa que 6 resultados igualmente espaçados também seriam suspeitos. No OP, 6 valores estão em uma faixa de 32k / 4M, ou <1% da faixa desejada. A probabilidade de ser um falso positivo é pequena demais para ser discutida.
Steve Jessop de

0

A solução dada pelo homem 3 rand para um número entre 1 e 10 inclusive é:

j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0)));

No seu caso, seria:

j = min + (int) ((max-min+1) * (rand() / (RAND_MAX + 1.0)));

Claro, isso não é aleatoriedade ou uniformidade perfeita, como algumas outras mensagens estão apontando, mas isso é suficiente para a maioria dos casos.


1
Isso meramente reorganiza a distribuição para parecer mais uniforme, mas na verdade não é mais uniforme para grandes intervalos (como o caso do OP)
Mooing Duck

0

@Solução ((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

Aviso : Não se esqueça, devido ao alongamento e possíveis erros de precisão (mesmo se RAND_MAX fosse grande o suficiente), você só poderá gerar "caixas" distribuídas uniformemente e nem todos os números em [min, max].


@Solution: Bigrand

Aviso : Observe que isso dobra os bits, mas ainda não será capaz de gerar todos os números em seu intervalo em geral, ou seja, não é necessariamente verdade que BigRand () irá gerar todos os números entre em seu intervalo.


Informações : Sua abordagem (módulo) é "boa", desde que o intervalo de rand () exceda o seu intervalo de intervalo e rand () seja "uniforme". O erro para no máximo os primeiros números máx. - mín. É 1 / (RAND_MAX +1).

Além disso, sugiro mudar para o novo pacote aleatório e em C ++ 11 também, que oferece melhores e mais variedades de implementações do que rand ().


0

Esta é a solução que encontrei:

#include "<stdlib.h>"

int32_t RandomRange(int32_t min, int32_t max) {
    return (rand() * (max - min + 1) / (RAND_MAX + 1)) + min;
}

Esta é uma solução de balde, conceitualmente semelhante às soluções que usam rand() / RAND_MAXpara obter um intervalo de ponto flutuante entre 0-1 e, em seguida, arredondar para um balde. No entanto, ele usa matemática puramente inteira e aproveita as vantagens do piso de divisão inteira para arredondar o valor para o intervalo mais próximo.

Faz algumas suposições. Primeiro, ele assume que RAND_MAX * (max - min + 1)sempre caberá dentro de um int32_t. Se RAND_MAXfor 32767 e cálculos internos de 32 bits forem usados, o intervalo máximo que você pode ter é 32767. Se sua implementação tiver um RAND_MAX muito maior, você pode superar isso usando um número inteiro maior (como int64_t) para o cálculo. Em segundo lugar, se int64_tfor usado, mas RAND_MAXainda for 32767, em intervalos maiores do que RAND_MAXvocê começará a obter "buracos" nos números de saída possíveis. Este é provavelmente o maior problema com qualquer solução derivada do dimensionamento rand().

Os testes em um grande número de iterações, no entanto, mostram que esse método é muito uniforme para intervalos pequenos. No entanto, é possível (e provável) que matematicamente isso tenha um pequeno viés e possivelmente desenvolva problemas quando o intervalo se aproxima RAND_MAX. Teste você mesmo e decida se ele atende às suas necessidades.


-1

Obviamente, o código a seguir não fornecerá números aleatórios, mas números pseudoaleatórios. Use o seguinte código

#define QUICK_RAND(m,n) m + ( std::rand() % ( (n) - (m) + 1 ) )

Por exemplo:

int myRand = QUICK_RAND(10, 20);

Você deve ligar

srand(time(0));  // Initialize random number generator.

caso contrário, os números não serão quase aleatórios.


1
A questão é pedir uma distribuição uniforme. Esta solução proposta não produzirá uma distribuição uniforme. A Biblioteca C ++ padrão possui recursos para geração de números pseudo-aleatórios . Aqueles fazer proporcionar uma distribuição uniforme, se solicitado.
Inspecionável em

-3

Acabei de encontrar isso na Internet. Isso deve funcionar:

DWORD random = ((min) + rand()/(RAND_MAX + 1.0) * ((max) - (min) + 1));

Por favor, esclareça para que você precisa deles, existem muitos algoritmos para PRNGs por aí. Além disso, seria mais fácil se você editar sua pergunta principal em vez de postar as respostas.
Peterchen

Isso funciona melhor para mim ... Eu sou capaz de obter números aleatórios melhor distribuídos com esta fórmula ..
Anand

4
Se o seu intervalo exceder RAND_MAX, os resultados podem não ser uniformes. Ou seja, existem valores no intervalo que não serão representados, não importa quantas vezes você chame sua função.
dmckee --- ex-moderador gatinho

4
Além disso, se max e min forem ambos int sem sinal, e min for 0 e max for MAX_UINT, então ((max) - (min) +1) será 0 e o resultado será 0 sempre. Cuidado com o estouro fazendo esse tipo de matemática! Conforme observado por dmckee, isso estende a distribuição sobre o intervalo de destino, mas não garante mais do que valores exclusivos RAND_MAX.
jesup
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.