O mesmo rand()
ocorre com um gerador de números pseudo-aleatórios que escolhe um número natural entre 0 e RAND_MAX
, que é uma constante definida em cstdlib
(consulte este artigo para uma visão geral sobre rand()
).
Agora, o que acontece se você deseja gerar um número aleatório entre digamos 0 e 2? Por uma questão de explicação, digamos que RAND_MAX
seja 10 e decido gerar um número aleatório entre 0 e 2 ligando rand()%3
. No entanto, rand()%3
não produz números entre 0 e 2 com igual probabilidade!
Quando rand()
retorna 0, 3, 6 ou 9 rand()%3 == 0
,. Portanto, P (0) = 4/11
Quando rand()
retorna 1, 4, 7 ou 10 rand()%3 == 1
,. Portanto, P (1) = 4/11
Quando rand()
retorna 2, 5 ou 8 rand()%3 == 2
,. Portanto, P (2) = 3/11
Isso não gera os números entre 0 e 2 com igual probabilidade. Obviamente, para faixas pequenas, esse pode não ser o maior problema, mas para uma faixa maior isso pode distorcer a distribuição, influenciando os números menores.
Então, quando rand()%n
retorna um intervalo de números de 0 a n-1 com igual probabilidade? Quando RAND_MAX%n == n - 1
. Nesse caso, junto com nossa suposição anterior rand()
, retorna um número entre 0 e RAND_MAX
com igual probabilidade, as classes de módulo de n também seriam igualmente distribuídas.
Então, como resolvemos esse problema? Uma maneira simples é continuar gerando números aleatórios até você obter um número no intervalo desejado:
int x;
do {
x = rand();
} while (x >= n);
mas isso é ineficiente para valores baixos de n
, pois você só tem uma n/RAND_MAX
chance de obter um valor no seu intervalo e, portanto, precisará realizar RAND_MAX/n
chamadas rand()
em média.
Uma abordagem fórmula mais eficaz seria a de levar algum grande gama com um divisível comprimento por n
, como RAND_MAX - RAND_MAX % n
, manter a geração de números aleatórios até que você obtenha um que mentiras na faixa, e em seguida, tomar o módulo:
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
Para valores pequenos de n
, isso raramente exigirá mais de uma chamada rand()
.
Trabalhos citados e leituras adicionais:
RAND_MAX%n == n - 1
_ _ é(RAND_MAX + 1) % n == 0
. Ao ler o código, costumo entender% something == 0
como "igualmente divisível" mais facilmente do que outras maneiras de calculá-lo. Obviamente, se o seu stdlib em C ++ tiverRAND_MAX
o mesmo valor queINT_MAX
,(RAND_MAX + 1)
certamente não funcionaria; portanto, o cálculo de Mark continua sendo a implementação mais segura.Continue selecionando uma opção aleatória é uma boa maneira de remover o viés.
Atualizar
Poderíamos tornar o código rápido se procurarmos um x no intervalo divisível por
n
.O loop acima deve ser muito rápido, digamos 1 iteração, em média.
fonte
rand()
pode retornar não for múltiplo den
, faça o que fizer, inevitavelmente você receberá um 'viés de módulo', a menos que descartar alguns desses valores. user1413793 explica isso muito bem (embora a solução proposta nessa resposta seja realmente ruim).RAND_MAX+1 - (RAND_MAX+1) % n
trabalho corretamente, mas ainda acho que deve ser escrito quantoRAND_MAX+1 - ((RAND_MAX+1) % n)
à clareza.RAND_MAX == INT_MAX
(como acontece na maioria dos sistemas) . Veja meu segundo comentário para @ user1413793 acima.@ user1413793 está correto sobre o problema. Não vou discutir isso mais além, exceto para dizer um ponto: sim, para valores pequenos
n
e grandesRAND_MAX
, o viés do módulo pode ser muito pequeno. Mas usar um padrão de indução de viés significa que você deve considerar o viés toda vez que calcular um número aleatório e escolher padrões diferentes para casos diferentes. E se você fizer a escolha errada, os bugs introduzidos são sutis e quase impossíveis de realizar testes de unidade. Comparado a apenas usar a ferramenta adequada (comoarc4random_uniform
), isso é trabalho extra, não menos trabalho. Fazer mais trabalho e obter uma solução pior é uma engenharia terrível, especialmente quando é sempre bom fazer isso na maioria das plataformas.Infelizmente, as implementações da solução são todas incorretas ou menos eficientes do que deveriam. (Cada solução tem vários comentários que explicam os problemas, mas nenhuma das soluções foi corrigida para resolvê-los.) Isso provavelmente confunde quem procura respostas, por isso estou fornecendo uma implementação em bom estado aqui.
Novamente, a melhor solução é apenas usar
arc4random_uniform
nas plataformas que a fornecem, ou uma solução à distância semelhante para sua plataforma (comoRandom.nextInt
em Java). Ele fará a coisa certa sem nenhum custo de código para você. Esta é quase sempre a decisão correta a ser feita.Se você não tiver
arc4random_uniform
, poderá usar o poder do código-fonte aberto para ver exatamente como ele é implementado em um RNG de maior alcance (ar4random
nesse caso, mas uma abordagem semelhante também pode funcionar em cima de outros RNGs).Aqui está a implementação do OpenBSD :
Vale ressaltar o último comentário de confirmação desse código para aqueles que precisam implementar coisas semelhantes:
A implementação Java também é facilmente localizável (consulte o link anterior):
fonte
arcfour_random()
realmente usar o algoritmo RC4 real em sua implementação, a saída definitivamente terá algum viés. Esperamos que os autores da sua biblioteca tenham passado a usar um CSPRNG melhor por trás da mesma interface. Lembro-me de que um dos BSDs atualmente usa o algoritmo ChaCha20 para implementararcfour_random()
. Mais informações sobre os preconceitos RC4 saída que torná-lo inútil para a segurança ou outras aplicações críticas, tais como vídeo poker: blog.cryptographyengineering.com/2013/03/.../dev/random
também usou o RC4 em algumas plataformas no passado (o Linux usa SHA-1 no modo contador). Infelizmente, as páginas de manual que encontrei por meio de pesquisa indicam que o RC4 ainda está em uso em várias plataformas que oferecemarc4random
(embora o código real possa ser diferente).-upper_bound % upper_bound == 0
??-upper_bound % upper_bound
será realmente 0 seint
for maior que 32 bits. Deveria ser(u_int32_t)-upper_bound % upper_bound)
(assumindo queu_int32_t
seja um BSD-ism parauint32_t
).Definição
Viés do módulo é o viés inerente ao uso da aritmética do módulo para reduzir um conjunto de saída para um subconjunto do conjunto de entrada. Em geral, existe um viés sempre que o mapeamento entre o conjunto de entrada e saída não é igualmente distribuído, como no caso de usar aritmética de módulo quando o tamanho do conjunto de saída não é um divisor do tamanho do conjunto de entrada.
Esse viés é particularmente difícil de evitar na computação, onde os números são representados como cadeias de bits: 0s e 1s. Encontrar fontes verdadeiramente aleatórias de aleatoriedade também é extremamente difícil, mas está além do escopo desta discussão. Para o restante desta resposta, suponha que exista uma fonte ilimitada de bits verdadeiramente aleatórios.
Exemplo de Problema
Vamos considerar a simulação de uma rolagem de dados (0 a 5) usando esses bits aleatórios. Como existem 6 possibilidades, precisamos de bits suficientes para representar o número 6, que é 3 bits. Infelizmente, três bits aleatórios produzem 8 resultados possíveis:
Podemos reduzir o tamanho do resultado definido para exatamente 6 assumindo o valor módulo 6, no entanto, isso apresenta o problema de polarização do módulo :
110
gera um 0 e111
gera um 1. Esse dado é carregado.Soluções Potenciais
Abordagem 0:
Em vez de confiar em bits aleatórios, em teoria, alguém poderia contratar um pequeno exército para rolar dados o dia todo e registrar os resultados em um banco de dados, e depois usar cada resultado apenas uma vez. Isso é tão prático quanto parece, e mais do que provavelmente não produziria resultados verdadeiramente aleatórios de qualquer maneira (trocadilhos).
Abordagem 1:
Em vez de usar o módulo, uma solução ingénuo mas matematicamente correcto é a resultados de descarte que o rendimento
110
e111
e simplesmente tentar novamente com 3 novos bits. Infelizmente, isso significa que há uma chance de 25% em cada rolagem de que será necessária uma repetição, incluindo cada uma delas . Isso é claramente impraticável para todos, exceto para os usos mais triviais.Abordagem 2:
Use mais bits: em vez de 3 bits, use 4. Isso gera 16 resultados possíveis. Obviamente, relançar sempre que o resultado for maior que 5 piora as coisas (10/16 = 62,5%), para que sozinho não ajude.
Observe que 2 * 6 = 12 <16, para que possamos obter com segurança qualquer resultado menor que 12 e reduzir esse módulo 6 para distribuir uniformemente os resultados. Os outros quatro resultados devem ser descartados e, em seguida, relançados como na abordagem anterior.
Parece bom no começo, mas vamos verificar a matemática:
Esse resultado é lamentável, mas vamos tentar novamente com 5 bits:
Uma melhoria definitiva, mas não boa o suficiente em muitos casos práticos. A boa notícia é que adicionar mais bits nunca aumentará as chances de precisar descartar e relançar . Isso vale não apenas para dados, mas em todos os casos.
Como demonstrado , no entanto, adicionar um bit extra pode não mudar nada. De fato, se aumentarmos nosso rolo para 6 bits, a probabilidade permanecerá 6,25%.
Isso gera 2 perguntas adicionais:
Solução Geral
Felizmente, a resposta para a primeira pergunta é sim. O problema com 6 é que 2 ^ x mod 6 alterna entre 2 e 4, que coincidentemente são um múltiplo de 2 um do outro, de modo que, para um x uniforme> 1,
Assim, 6 é uma exceção e não a regra. É possível encontrar módulos maiores que produzam poderes consecutivos de 2 da mesma maneira, mas eventualmente isso deve ser contornado, e a probabilidade de um descarte será reduzida.
Prova de conceito
Aqui está um exemplo de programa que usa o libcrypo do OpenSSL para fornecer bytes aleatórios. Ao compilar, certifique-se de vincular à biblioteca com a
-lcrypto
qual a maioria das pessoas deve ter disponível.Encorajo a jogar com os valores
MODULUS
eROLLS
para ver quantas repetições realmente acontecem na maioria das condições. Uma pessoa cética também pode querer salvar os valores calculados em arquivo e verificar se a distribuição parece normal.fonte
randomPool = RAND_bytes(...)
linha sempre resultarárandomPool == 1
devido à afirmação. Isso sempre resulta em um descarte e um relançamento. Eu acho que você queria declarar em uma linha separada. Conseqüentemente, isso fez com que o RNG retornasse1
para cada iteração.randomPool
sempre será avaliado de1
acordo com a documentaçãoRAND_bytes()
do OpenSSL , pois ele sempre será bem-sucedido graças àRAND_status()
asserção.Existem duas queixas usuais com o uso do módulo.
um é válido para todos os geradores. É mais fácil ver em um caso limite. Se o seu gerador tiver um RAND_MAX que é 2 (que não é compatível com o padrão C) e você deseja apenas 0 ou 1 como valor, o uso do módulo gerará 0 duas vezes mais (quando o gerador gerar 0 e 2) gerar 1 (quando o gerador gerar 1). Observe que isso é verdade assim que você não descarta valores, qualquer que seja o mapeamento que você está usando dos valores do gerador para o desejado, um ocorrerá duas vezes mais que o outro.
algum tipo de gerador tem seus bits menos significativos menos aleatórios que o outro, pelo menos para alguns de seus parâmetros, mas, infelizmente, esses parâmetros têm outra característica interessante (como ter RAND_MAX um a menos que uma potência de 2). O problema é bem conhecido e, por um longo tempo, a implementação da biblioteca provavelmente evita o problema (por exemplo, a implementação de amostra rand () no padrão C usa esse tipo de gerador, mas descarta os 16 bits menos significativos), mas alguns gostam de reclamar isso e você pode ter azar
Usando algo como
gerar um número aleatório entre 0 e n evitará os dois problemas (e evita o estouro com RAND_MAX == INT_MAX)
BTW, C ++ 11 introduziu maneiras padrão para a redução e outro gerador que não rand ().
fonte
A solução de Mark (a solução aceita) é quase perfeita.
No entanto, há uma ressalva que descarta 1 conjunto válido de resultados em qualquer cenário em que
RAND_MAX
(RM
) é 1 menor que um múltiplo deN
(OndeN
= o número possível de resultados válidos).ou seja, quando a 'contagem de valores descartados' (
D
) é igual aN
, então eles são realmente um conjunto válido (V)
, não um conjunto inválido (I
).O que causa isso é que, em algum momento, Mark perde de vista a diferença entre
N
eRand_Max
.N
é um conjunto cujos membros válidos são compostos apenas por números inteiros positivos, pois contém uma contagem de respostas que seriam válidas. (por exemplo: SetN
={1, 2, 3, ... n }
)Rand_max
No entanto, é um conjunto que (conforme definido para nossos propósitos) inclui qualquer número de números inteiros não negativos.Em sua forma mais genérica, o que é definido aqui como
Rand Max
é o Conjunto de todos os resultados válidos, que teoricamente podem incluir números negativos ou valores não numéricos.Portanto,
Rand_Max
é melhor definido como o conjunto de "Respostas possíveis".No entanto,
N
opera contra a contagem dos valores dentro do conjunto de respostas válidas, portanto, mesmo conforme definido em nosso caso específico,Rand_Max
será um valor um a menos que o número total que ele contém.Usando a solução de Mark, os valores são descartados quando: X => RM - RM% N
Como você pode ver no exemplo acima, quando o valor de X (o número aleatório que obtemos da função inicial) é 252, 253, 254 ou 255, nós o descartávamos, mesmo que esses quatro valores incluam um conjunto válido de valores retornados .
IE: Quando a contagem dos valores Descartados (I) = N (O número de resultados válidos), um conjunto válido de valores de retorno será descartado pela função original.
Se descrevermos a diferença entre os valores N e RM como D, ou seja:
Então, à medida que o valor de D se torna menor, a Porcentagem de relançamentos desnecessários devido a esse método aumenta a cada multiplicativo natural. (Quando RAND_MAX NÃO é igual a um número primo, isso é uma preocupação válida)
POR EXEMPLO:
Como a porcentagem de Rerolls necessários aumenta quanto mais N chega ao RM, isso pode ser uma preocupação válida para muitos valores diferentes, dependendo das restrições do sistema que ele está executando e dos valores que estão sendo procurados.
Para negar isso, podemos fazer uma alteração simples, como mostrado aqui:
Isso fornece uma versão mais geral da fórmula, que explica as peculiaridades adicionais do uso do módulo para definir seus valores máximos.
Exemplos de uso de um valor pequeno para RAND_MAX, que é um multiplicativo de N.
Mark'original Version:
Versão Generalizada 1:
Além disso, no caso em que N deve ser o número de valores em RAND_MAX; nesse caso, você pode definir N = RAND_MAX +1, a menos que RAND_MAX = INT_MAX.
Em termos de loop, você pode simplesmente usar N = 1, e qualquer valor de X será aceito, no entanto, e inserir uma instrução IF para o seu multiplicador final. Mas talvez você tenha um código que possa ter um motivo válido para retornar 1 quando a função for chamada com n = 1 ...
Portanto, pode ser melhor usar 0, o que normalmente forneceria um erro Div 0, quando você deseja ter n = RAND_MAX + 1
Versão generalizada 2:
Ambas as soluções resolvem o problema com resultados válidos descartados desnecessariamente, que ocorrerão quando RM + 1 for um produto de n.
A segunda versão também aborda o cenário de casos extremos quando você precisa de n para igualar o conjunto total possível de valores contidos em RAND_MAX.
A abordagem modificada em ambos é a mesma e permite uma solução mais geral para a necessidade de fornecer números aleatórios válidos e minimizar os valores descartados.
Reiterar:
A solução geral básica que amplia o exemplo da marca:
A solução geral estendida que permite um cenário adicional de RAND_MAX + 1 = n:
Em alguns idiomas (idiomas especialmente interpretados), fazer os cálculos da operação de comparação fora da condição while pode levar a resultados mais rápidos, pois esse é um cálculo único, independentemente de quantas tentativas forem necessárias. YMMV!
fonte
RAND_MAX%n = n - 1
Com um
RAND_MAX
valor de3
(na realidade, deve ser muito maior que isso, mas o viés ainda existiria), faz sentido a partir desses cálculos que existe um viés:1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1
Nesse caso,
% 2
é isso que você não deve fazer quando quiser um número aleatório entre0
e1
. Você pode obter um número aleatório entre0
e2
fazendo isso% 3
, porque neste caso:RAND_MAX
é um múltiplo de3
.Outro método
Há muito mais simples, mas para adicionar a outras respostas, eis a minha solução para obter um número aleatório entre
0
en - 1
, portanton
, possibilidades diferentes, sem viés.>= n
, reinicie (sem módulo).Não é fácil obter dados realmente aleatórios, por que usar mais bits do que o necessário?
Abaixo está um exemplo no Smalltalk, usando um cache de bits de um gerador de números pseudo-aleatórios. Como não sou especialista em segurança, use por sua conta e risco.
fonte
Como a resposta aceita indica, o "viés do módulo" tem suas raízes no baixo valor de
RAND_MAX
. Ele usa um valor extremamente pequeno deRAND_MAX
(10) para mostrar que se RAND_MAX fosse 10, você tentaria gerar um número entre 0 e 2 usando%, resultariam nos seguintes resultados:Portanto, existem 4 saídas de 0 (chance 4/10) e apenas 3 saídas de 1 e 2 (3/10 chances cada).
Então é tendencioso. Os números mais baixos têm uma chance melhor de sair.
Mas isso só aparece tão obviamente quando
RAND_MAX
é pequeno . Ou, mais especificamente, quando o número pelo qual você está modificando é grande em comparação comRAND_MAX
.Uma solução muito melhor do que o loop (que é incrivelmente ineficiente e nem deveria ser sugerido) é usar um PRNG com uma faixa de saída muito maior. O algoritmo Mersenne Twister tem uma saída máxima de 4.294.967.295. Como tal,
MersenneTwister::genrand_int32() % 10
para todos os efeitos, será igualmente distribuído e o efeito do viés do módulo desaparecerá.fonte
MT::genrand_int32()%2
escolhe 0 (50 + 2,3e-8)% do tempo e 1 (50 - 2,3e-8)% do tempo. A menos que você esteja construindo o RGN de um cassino (para o qual provavelmente usaria um RGN de alcance muito maior), qualquer usuário não notará 2,3 e 8% a mais do tempo. Você está falando de números pequenos demais para importar aqui.RAND_MAX
valor alto diminuirá o viés do módulo, mas não o eliminará. Looping vontade.RAND_MAX
for suficientemente maior que o número pelo qual você está modificando, o número de vezes que você precisa regenerar o número aleatório é muito pequeno e não afetará a eficiência. Eu digo para manter o loop, desde que você esteja testando contra o maior múltiplo de,n
e não apenasn
conforme proposto pela resposta aceita.Acabei de escrever um código para o Método de Moeda Imparcial de Von Neumann, que teoricamente deveria eliminar qualquer viés no processo de geração de números aleatórios. Mais informações podem ser encontradas em ( http://en.wikipedia.org/wiki/Fair_coin )
fonte
rand() % 100
100 vezes. B) se todos os resultados forem diferentes, pegue o primeiro. C) caso contrário, GOTO A. Isso funcionará, mas com um número esperado de iterações de cerca de 10 ^ 42, você precisará ser bastante paciente. E imortal.else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}