Eu fiz uma aula chamada QuickRandom
, e seu trabalho é produzir números aleatórios rapidamente. É realmente simples: basta pegar o valor antigo, multiplicar por a double
e pegar a parte decimal.
Aqui está minha QuickRandom
turma na íntegra:
public class QuickRandom {
private double prevNum;
private double magicNumber;
public QuickRandom(double seed1, double seed2) {
if (seed1 >= 1 || seed1 < 0) throw new IllegalArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1);
prevNum = seed1;
if (seed2 <= 1 || seed2 > 10) throw new IllegalArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2);
magicNumber = seed2;
}
public QuickRandom() {
this(Math.random(), Math.random() * 10);
}
public double random() {
return prevNum = (prevNum*magicNumber)%1;
}
}
E aqui está o código que escrevi para testá-lo:
public static void main(String[] args) {
QuickRandom qr = new QuickRandom();
/*for (int i = 0; i < 20; i ++) {
System.out.println(qr.random());
}*/
//Warm up
for (int i = 0; i < 10000000; i ++) {
Math.random();
qr.random();
System.nanoTime();
}
long oldTime;
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
Math.random();
}
System.out.println(System.nanoTime() - oldTime);
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
qr.random();
}
System.out.println(System.nanoTime() - oldTime);
}
É um algoritmo muito simples que simplesmente multiplica o dobro anterior por um "número mágico" duplo. Eu juntei tudo rapidamente, então provavelmente poderia melhorar, mas estranhamente, parece estar funcionando bem.
Esta é uma saída de amostra das linhas comentadas no main
método:
0.612201846732229
0.5823974655091941
0.31062451498865684
0.8324473610354004
0.5907187526770246
0.38650264675748947
0.5243464344127049
0.7812828761272188
0.12417247811074805
0.1322738256858378
0.20614642573072284
0.8797579436677381
0.022122999476108518
0.2017298328387873
0.8394849894162446
0.6548917685640614
0.971667953190428
0.8602096647696964
0.8438709031160894
0.694884972852229
Hum. Bastante aleatório. De fato, isso funcionaria para um gerador de números aleatórios em um jogo.
Aqui está um exemplo de saída da parte não comentada:
5456313909
1427223941
Uau! Ele executa quase 4 vezes mais rápido que Math.random
.
Lembro-me de ler em algum lugar que Math.random
usava System.nanoTime()
e toneladas de módulos malucos e coisas de divisão. Isso é mesmo necessário? Meu algoritmo executa muito mais rápido e parece bastante aleatório.
Eu tenho duas perguntas:
- Meu algoritmo é "bom o suficiente" (para, digamos, um jogo, onde números realmente aleatórios não são muito importantes)?
- Por que
Math.random
fazer tanto quando parece simples multiplicação e cortar o decimal é suficiente?
fonte
new QuickRandom(0,5)
ounew QuickRandom(.5, 2)
. Ambos produzirão repetidamente 0 para o seu número.Respostas:
Sua
QuickRandom
implementação não tem realmente uma distribuição uniforme. As frequências são geralmente mais altas nos valores mais baixos, enquantoMath.random()
tem uma distribuição mais uniforme. Aqui está um SSCCE que mostra que:O resultado médio é assim:
Se você repetir o teste, verá que a distribuição QR varia muito, dependendo das sementes iniciais, enquanto a distribuição MR é estável. Às vezes, atinge a distribuição uniforme desejada, mas mais do que frequentemente não. Aqui está um dos exemplos mais extremos: está além dos limites do gráfico:
fonte
QuickRandom
. Às vezes, é quase uniforme, às vezes é muito pior que isso.O que você está descrevendo é um tipo de gerador aleatório chamado gerador congruencial linear . O gerador funciona da seguinte maneira:
Este gerador tem muitas propriedades agradáveis, mas tem problemas significativos como uma boa fonte aleatória. O artigo da Wikipedia vinculado acima descreve alguns dos pontos fortes e fracos. Em resumo, se você precisar de bons valores aleatórios, essa provavelmente não é uma abordagem muito boa.
Espero que isto ajude!
fonte
Sua função de número aleatório é ruim, pois possui muito pouco estado interno - o número de saída da função em qualquer etapa é totalmente dependente do número anterior. Por exemplo, se assumirmos que
magicNumber
é 2 (a título de exemplo), a sequência:é fortemente espelhado por sequências semelhantes:
Em muitos casos, isso gerará correlações visíveis em seu jogo - por exemplo, se você fizer chamadas sucessivas para sua função para gerar coordenadas X e Y para objetos, os objetos formarão padrões diagonais claros.
A menos que você tenha boas razões para acreditar que o gerador de números aleatórios está atrasando seu aplicativo (e isso é MUITO improvável), não há boas razões para tentar escrever por conta própria.
fonte
O verdadeiro problema disso é que o histograma de saída depende muito da semente inicial - na maioria das vezes acabará com uma saída quase uniforme, mas na maioria das vezes terá uma saída distintamente não uniforme.
Inspirado neste artigo sobre o quão ruim é a
rand()
função do php, criei algumas imagens de matriz aleatória usandoQuickRandom
eSystem.Random
. Esta execução mostra como às vezes a semente pode ter um efeito ruim (neste caso, favorecendo números mais baixos), ondeSystem.Random
é bastante uniforme.QuickRandom
System.Random
Pior ainda
Se inicializarmos
QuickRandom
conformenew QuickRandom(0.01, 1.03)
obtemos esta imagem:O código
fonte
magicNumber
multiplicação produz um número semelhante aprevNum
, o que mostra a falta de aleatoriedade. Se usarmos as sementesnew QuickRandom(0.01, 1.03)
, obtemos este i.imgur.com/Q1Yunbe.png !System.Drawing.Bitmap
.Um problema com seu gerador de números aleatórios é que não existe um 'estado oculto' - se eu souber qual número aleatório você retornou na última chamada, conheço todos os números aleatórios que você enviará até o final dos tempos, pois existe apenas um possível próximo resultado, e assim por diante.
Outra coisa a considerar é o 'período' do seu gerador de números aleatórios. Obviamente, com um tamanho de estado finito, igual à porção mantissa de um duplo, ele só poderá retornar no máximo 2 ^ 52 valores antes do loop. Mas esse é o melhor caso - você pode provar que não há loops do período 1, 2, 3, 4 ...? Se houver, seu RNG terá um comportamento terrível e degenerado nesses casos.
Além disso, sua geração de números aleatórios terá uma distribuição uniforme para todos os pontos de partida? Caso contrário, seu RNG será tendencioso - ou pior, tendencioso de maneiras diferentes, dependendo da semente inicial.
Se você pode responder a todas essas perguntas, incrível. Se você não pode, então você sabe por que a maioria das pessoas não reinventa a roda e usa um gerador de números aleatórios comprovado;)
(A propósito, um bom ditado é: O código mais rápido é o código que não é executado. Você pode fazer o aleatório mais rápido () do mundo, mas não é bom se não for muito aleatório)
fonte
0 -> 0
. Dependendo da semente, pode haver muitas outras. (Por exemplo, com uma semente de 3,0,0.5 -> 0.5
,0.25 -> 0.75 -> 0.25
,0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2
, etc.)Um teste comum que sempre fiz ao desenvolver PRNGs foi:
Isso permitiu que eu iterasse rapidamente em idéias que eram PRNGs "suficientemente boas" para seqüências de 1 a 20 megabytes. Ele também forneceu uma imagem de cima para baixo melhor do que apenas examiná-la a olho, pois qualquer PRNG "bom o suficiente" com meia palavra de estado poderia rapidamente exceder a capacidade dos olhos de ver o ponto do ciclo.
Se eu fosse realmente exigente, poderia pegar os bons algoritmos e executar os testes DIEHARD / NIST neles, para obter mais informações e depois voltar e ajustar um pouco mais.
A vantagem do teste de compressão, ao contrário de uma análise de frequência, é que, trivialmente, é fácil construir uma boa distribuição: basta gerar um bloco de 256 comprimentos contendo todos os caracteres dos valores de 0 a 255 e fazer isso 100.000 vezes. Mas essa sequência tem um ciclo de comprimento 256.
Uma distribuição distorcida, mesmo que por uma pequena margem, deve ser captada por um algoritmo de compactação, principalmente se você fornecer o suficiente (digamos 1 megabyte) da sequência para trabalhar. Se alguns caracteres, bigrams ou n-gramas ocorrerem com mais frequência, um algoritmo de compactação pode codificar essa inclinação da distribuição para códigos que favorecem as ocorrências frequentes com palavras de código mais curtas e você obtém um delta de compactação.
Como a maioria dos algoritmos de compactação é rápida e não requer implementação (já que os SOs os estão por aí), o teste de compactação é muito útil para classificar rapidamente a aprovação / reprovação de um PRNG que você possa estar desenvolvendo.
Boa sorte com seus experimentos!
Ah, eu realizei esse teste na rng que você tem acima, usando o seguinte mod pequeno do seu código:
Os resultados foram:
Eu consideraria um PRNG bom se o arquivo de saída não pudesse ser compactado. Para ser honesto, não achei que o seu PRNG se sairia tão bem, apenas 16% em ~ 20 Megs é bastante impressionante para uma construção tão simples. Mas ainda considero um fracasso.
fonte
O gerador aleatório mais rápido que você pode implementar é este:
XD, brinca à parte, além de tudo o que foi dito aqui, eu gostaria de contribuir citando que testar seqüências aleatórias "é uma tarefa difícil" [1], e existem vários testes que verificam certas propriedades de números pseudo-aleatórios. muitos deles aqui: http://www.random.org/analysis/#2005
Uma maneira simples de avaliar a "qualidade" do gerador aleatório é o antigo teste do qui quadrado.
Citação [1]
Usando essa teoria e o seguinte código:
Obtive o seguinte resultado:
Que, para o QuickRandom, está longe de r (fora de
r ± 2 * sqrt(r)
)Dito isto, o QuickRandom pode ser rápido, mas (como indicado em outras respostas) não é bom como um gerador de números aleatórios
[1] SEDGEWICK ROBERT, Algoritmos em C , Addinson Wesley Publishing Company, 1990, páginas 516 a 518
fonte
int[]
são inicializados em zero, portanto, não há necessidade dessa parte. A conversão para flutuar não faz sentido quando você trabalha com dobra. Por último: chamar os nomes de métodos random1 e random2 é bem engraçado.Eu montei uma rápida maquete do seu algoritmo em JavaScript para avaliar os resultados. Ele gera 100.000 números inteiros aleatórios de 0 a 99 e rastreia a instância de cada número inteiro.
A primeira coisa que noto é que é mais provável que você obtenha um número baixo do que um número alto. Você vê isso mais quando
seed1
está alto eseed2
baixo. Em alguns casos, obtive apenas três números.Na melhor das hipóteses, seu algoritmo precisa de algum aprimoramento.
fonte
Se a
Math.Random()
função chamar o sistema operacional para obter a hora do dia, você não poderá compará-lo à sua função. Sua função é um PRNG, enquanto essa função está buscando números aleatórios reais. Maçãs e laranjas.Seu PRNG pode ser rápido, mas não possui informações de estado suficientes para atingir um longo período antes da repetição (e sua lógica não é sofisticada o suficiente para atingir os períodos possíveis com tantas informações de estado).
Período é a duração da sequência antes que o seu PRNG comece a se repetir. Isso acontece assim que a máquina PRNG faz uma transição de estado para um estado que é idêntico a algum estado passado. A partir daí, ele repetirá as transições que começaram nesse estado. Outro problema com os PRNG pode ser um número baixo de sequências únicas, bem como convergência degenerada em uma sequência específica que se repete. Também pode haver padrões indesejáveis. Por exemplo, suponha que um PRNG pareça bastante aleatório quando os números são impressos em decimal, mas uma inspeção dos valores em binário mostra que o bit 4 está simplesmente alternando entre 0 e 1 em cada chamada. Opa!
Dê uma olhada no Mersenne Twister e outros algoritmos. Existem maneiras de encontrar um equilíbrio entre a duração do período e os ciclos da CPU. Uma abordagem básica (usada no Mersenne Twister) é percorrer o vetor de estado. Ou seja, quando um número está sendo gerado, ele não se baseia em todo o estado, apenas em algumas palavras da matriz de estados, sujeitas a operações de alguns bits. Mas a cada passo, o algoritmo também se move na matriz, embaralhando o conteúdo um pouco de cada vez.
fonte
/dev/random
é uma fonte de aleatoriedade real obtida de drivers de dispositivo, e não um PRNG. Ele bloqueia quando não há bits suficientes disponíveis. O dispositivo irmão/dev/urandom
também não bloqueia, mas ainda não é exatamente um PRNG, pois é atualizado com bits aleatórios quando estão disponíveis.nanoTime()
+ contador / hash são usados para a semente padrãojava.util.Random
do oracle / OpenJDK. Isso é apenas para a semente, então é um LCG padrão. De fato, o gerador OP utiliza 2 números aleatórios para a semente, o que é bom - então não há diferençajava.util.Random
.System.currentTimeMillis()
foi a semente padrão no JDK1.4-Existem muitos, muitos geradores de números pseudo-aleatórios por aí. Por exemplo, a matriz de Knuth , a Mersenne twister , ou procure geradores LFSR. O monumental "Algoritmos Seminuméricos" de Knuth analisa a área e propõe alguns geradores congruenciais lineares (simples de implementar, rápidos).
Mas eu sugiro que você se atenha ao
java.util.Random
orMath.random
, eles são rápidos e pelo menos aceitáveis para uso ocasional (por exemplo, jogos e coisas assim). Se você é paranóico na distribuição (algum programa de Monte Carlo ou um algoritmo genético), verifique a implementação deles (a fonte está disponível em algum lugar) e propague-os com um número verdadeiramente aleatório, no sistema operacional ou no random.org . Se isso for necessário para algum aplicativo em que a segurança é crítica, você terá que se aprofundar. E, como nesse caso, você não deve acreditar no que algum quadrado colorido com os bits faltando vaza aqui, vou calar a boca agora.fonte
É muito improvável que o desempenho da geração de números aleatórios seja um problema para qualquer caso de uso que você tenha, a menos que acesse uma única
Random
instância de vários encadeamentos (porqueRandom
ésynchronized
).No entanto, se esse for realmente o caso e você precisar de muitos números aleatórios rapidamente, sua solução não será confiável. Às vezes, dá bons resultados, às vezes, resultados horríveis (com base nas configurações iniciais).
Se você deseja os mesmos números que a
Random
classe fornece, apenas mais rapidamente, você pode se livrar da sincronização:Simplesmente peguei o
java.util.Random
código e removi a sincronização, que resulta no dobro do desempenho em comparação com o original no meu Oracle HotSpot JVM 7u9. Ainda é mais lento que o seuQuickRandom
, mas fornece resultados muito mais consistentes. Para ser preciso, para os mesmosseed
valores e aplicativos de encadeamento único, ele fornece os mesmos números pseudo-aleatórios daRandom
classe original .Este código é baseado na corrente
java.util.Random
no OpenJDK 7u, licenciada sob o GNU GPL v2 .EDIT 10 meses depois:
Acabei de descobrir que você nem precisa usar meu código acima para obter uma
Random
instância não sincronizada . Também há um no JDK!Veja a
ThreadLocalRandom
classe do Java 7 . O código dentro dele é quase idêntico ao meu código acima. A classe é simplesmente umaRandom
versão isolada de encadeamento local, adequada para gerar números aleatórios rapidamente. A única desvantagem em que consigo pensar é que você não pode defini-laseed
manualmente.Exemplo de uso:
fonte
:)
Isso é interessante, obrigado!'Aleatório' é mais do que apenas obter números ... o que você tem é pseudo-aleatório
Se o pseudo-aleatório for bom o suficiente para seus propósitos, então é muito mais rápido (e o XOR + Bitshift será mais rápido do que você tem)
Rolf
Editar:
OK, depois de ser muito apressado nesta resposta, deixe-me responder a verdadeira razão pela qual seu código é mais rápido:
No JavaDoc for Math.Random ()
É provavelmente por isso que seu código é mais rápido.
fonte
Math.random()
é mais lenta. Teria que sincronizar ou criar um novoRandom
todas as vezes, e nenhum deles seria muito atraente em termos de desempenho. Se eu me importasse com o desempenho, criaria o meu próprionew Random
e apenas o usaria. : PO java.util.Random não é muito diferente, um LCG básico descrito por Knuth. No entanto, possui as 2 principais vantagens / diferenças principais:
Abaixo está a rotina principal que gera números inteiros 'aleatórios' em java.util.Random.
Se você remover o AtomicLong e o estado não revelado (ou seja, usando todos os bits do
long
), obterá mais desempenho do que a multiplicação / módulo duplo.Última observação:
Math.random
não deve ser usado para nada além de testes simples, é propenso a contendas e, se você tem alguns threads chamando simultaneamente, o desempenho diminui. Uma característica histórica pouco conhecida é a introdução do CAS em java - para superar uma referência infame (primeiro pela IBM via intrínseca e depois pela Sun criou o "CAS from Java")fonte
Esta é a função aleatória que uso nos meus jogos. É bem rápido e tem boa distribuição (suficiente).
fonte
volatile
o compilador é livre para eliminar (ou introduzir) variáveis locais à vontade.