Hice una clase llamada QuickRandom
, y su trabajo es producir números aleatorios rápidamente. Es realmente simple: solo toma el valor anterior, multiplica por a double
y toma la parte decimal.
Aquí está mi QuickRandom
clase en su totalidad:
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;
}
}
Y aquí está el código que escribí para probarlo:
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);
}
Es un algoritmo muy simple que simplemente multiplica el doble anterior por un "número mágico" doble. Lo uní bastante rápido, por lo que probablemente podría mejorarlo, pero extrañamente, parece estar funcionando bien.
Esta es una salida de muestra de las líneas comentadas en el 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
Hm. Bastante al azar. De hecho, eso funcionaría para un generador de números aleatorios en un juego.
Aquí está la salida de muestra de la parte no comentada:
5456313909
1427223941
¡Guauu! Se realiza casi 4 veces más rápido que Math.random
.
Recuerdo haber leído en algún lugar que Math.random
usaba System.nanoTime()
toneladas de módulo loco y cosas de división. ¿Es eso realmente necesario? Mi algoritmo funciona mucho más rápido y parece bastante aleatorio.
Tengo dos preguntas:
- ¿Es mi algoritmo "suficientemente bueno" (para, por ejemplo, un juego, donde los números realmente aleatorios no son demasiado importantes)?
- ¿Por qué hace
Math.random
tanto cuando parece simple multiplicación y cortar el decimal será suficiente?
fuente
new QuickRandom(0,5)
onew QuickRandom(.5, 2)
. Ambos emitirán repetidamente 0 para su número.Respuestas:
Su
QuickRandom
implementación no tiene realmente una distribución uniforme. Las frecuencias son generalmente más altas en los valores más bajos, mientras queMath.random()
tiene una distribución más uniforme. Aquí hay un SSCCE que muestra que:El resultado promedio se ve así:
Si repite la prueba, verá que la distribución QR varía mucho, dependiendo de las semillas iniciales, mientras que la distribución MR es estable. A veces alcanza la distribución uniforme deseada, pero más de las veces no lo hace. Este es uno de los ejemplos más extremos, está incluso más allá de los límites del gráfico:
fuente
QuickRandom
. A veces, está cerca del uniforme, a veces es mucho peor que esto.Lo que está describiendo es un tipo de generador aleatorio llamado generador congruencial lineal . El generador funciona de la siguiente manera:
Este generador tiene muchas propiedades agradables, pero tiene problemas significativos como una buena fuente aleatoria. El artículo de Wikipedia vinculado anteriormente describe algunas de las fortalezas y debilidades. En resumen, si necesita buenos valores aleatorios, probablemente este no sea un enfoque muy bueno.
¡Espero que esto ayude!
fuente
La función de su número aleatorio es deficiente, ya que tiene muy poco estado interno: el número que genera la función en cualquier paso depende completamente del número anterior. Por ejemplo, si suponemos que
magicNumber
es 2 (a modo de ejemplo), entonces la secuencia:está fuertemente reflejado por secuencias similares:
En muchos casos, esto generará correlaciones notables en su juego; por ejemplo, si realiza llamadas sucesivas a su función para generar coordenadas X e Y para los objetos, los objetos formarán patrones diagonales claros.
A menos que tenga una buena razón para creer que el generador de números aleatorios está ralentizando su aplicación (y esto es MUY improbable), no hay una buena razón para intentar escribir la suya.
fuente
El verdadero problema con esto es que su histograma de salida depende en gran medida de la semilla inicial: la mayor parte del tiempo terminará con una salida casi uniforme, pero muchas veces tendrá una salida claramente no uniforme.
Inspirado en este artículo sobre cuán mala es la
rand()
función de php , hice algunas imágenes de matriz aleatorias usandoQuickRandom
ySystem.Random
. Esta ejecución muestra cómo a veces la semilla puede tener un efecto negativo (en este caso, favoreciendo números más bajos) dondeSystem.Random
es bastante uniforme.QuickRandom
System.Random
Peor aún
Si inicializamos a
QuickRandom
medidanew QuickRandom(0.01, 1.03)
que obtenemos esta imagen:El código
fuente
magicNumber
multiplicación produce un número similar aprevNum
, que muestra la falta de aleatoriedad. Si usamos las semillasnew QuickRandom(0.01, 1.03)
, obtenemos esto i.imgur.com/Q1Yunbe.png !System.Drawing.Bitmap
.Un problema con su generador de números aleatorios es que no hay un 'estado oculto': si sé qué número aleatorio devolvió en la última llamada, sé cada número aleatorio que enviará hasta el final de los tiempos, ya que solo hay uno posible resultado siguiente, y así sucesivamente.
Otra cosa a considerar es el 'período' de su generador de números aleatorios. Obviamente, con un tamaño de estado finito, igual a la porción de mantisa de un doble, solo podrá devolver como máximo 2 ^ 52 valores antes del bucle. Pero ese es el mejor de los casos: ¿puede probar que no hay bucles de los períodos 1, 2, 3, 4 ...? Si los hay, su RNG tendrá un comportamiento horrible y degenerado en esos casos.
Además, ¿su generación de números aleatorios tendrá una distribución uniforme para todos los puntos de partida? Si no es así, su RNG estará sesgado, o peor, sesgado de diferentes maneras dependiendo de la semilla inicial.
Si puedes responder a todas estas preguntas, genial. Si no puede, entonces sabe por qué la mayoría de las personas no reinventan la rueda y usan un generador de números aleatorios comprobado;)
(Por cierto, un buen adagio es: el código más rápido es el código que no se ejecuta. Podría hacer el aleatorio () más rápido del mundo, pero no es bueno si no es muy aleatorio)
fuente
0 -> 0
. Dependiendo de la semilla, puede haber muchos otros. (Por ejemplo, con una semilla de 3,0,0.5 -> 0.5
,0.25 -> 0.75 -> 0.25
,0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2
, etc.)Una prueba común que siempre hacía cuando desarrollaba PRNG era:
Esto me permitió repetir rápidamente ideas que eran PRNG "suficientemente buenas" para secuencias de alrededor de 1 a 20 megabytes. También proporcionó una mejor imagen de arriba hacia abajo que simplemente inspeccionarla a simple vista, ya que cualquier PRNG "suficientemente bueno" con media palabra de estado podría exceder rápidamente la capacidad de sus ojos para ver el punto del ciclo.
Si fuera realmente exigente, podría tomar los buenos algoritmos y ejecutar las pruebas DIEHARD / NIST en ellos, para obtener más información, y luego regresar y ajustar un poco más.
La ventaja de la prueba de compresión, a diferencia de un análisis de frecuencia, es que, trivialmente, es fácil construir una buena distribución: simplemente genera un bloque de 256 longitudes que contenga todos los caracteres de los valores 0 - 255, y haz esto 100,000 veces. Pero esta secuencia tiene un ciclo de longitud 256.
Una distribución sesgada, incluso por un pequeño margen, debe ser recogida por un algoritmo de compresión, particularmente si le da suficiente (digamos 1 megabyte) de la secuencia para trabajar. Si algunos caracteres, o bigrams, o n-gramas ocurren con mayor frecuencia, un algoritmo de compresión puede codificar este sesgo de distribución a códigos que favorecen las ocurrencias frecuentes con palabras de código más cortas, y usted obtiene un delta de compresión.
Dado que la mayoría de los algoritmos de compresión son rápidos y no requieren implementación (ya que los sistemas operativos los tienen por ahí), la prueba de compresión es muy útil para calificar rápidamente la aprobación / falla de un PRNG que podría estar desarrollando.
¡Buena suerte con tus experimentos!
Oh, realicé esta prueba en el rng que tienes arriba, usando el siguiente pequeño mod de tu código:
Los resultados fueron:
Consideraría un PRNG bueno si el archivo de salida no se pudiera comprimir en absoluto. Para ser honesto, no pensé que tu PRNG lo haría tan bien, solo el 16% en ~ 20 Megs es bastante impresionante para una construcción tan simple. Pero todavía lo considero un fracaso.
fuente
El generador aleatorio más rápido que podría implementar es este:
XD, bromea aparte, además de todo lo que se dice aquí, me gustaría contribuir citando que probar secuencias aleatorias "es una tarea difícil" [1], y hay varias pruebas que verifican ciertas propiedades de números pseudoaleatorios, puede encontrar un Muchos de ellos aquí: http://www.random.org/analysis/#2005
Una forma sencilla de evaluar la "calidad" del generador aleatorio es la antigua prueba Chi Square.
Citando [1]
Usando esta teoría y el siguiente código:
Obtuve el siguiente resultado:
Que, para QuickRandom, está lejos de r (fuera de
r ± 2 * sqrt(r)
)Dicho esto, QuickRandom podría ser rápido pero (como se indicó en otras respuestas) no es bueno como generador de números aleatorios
[1] SEDGEWICK ROBERT, Algoritmos en C , Addinson Wesley Publishing Company, 1990, páginas 516 a 518
fuente
int[]
se inicializan a cero, por lo que no es necesario para esta parte. Lanzar para flotar no tiene sentido cuando trabajas con dobles. Por último: llamar a los nombres de los métodos random1 y random2 es bastante divertido.Reuní una maqueta rápida de su algoritmo en JavaScript para evaluar los resultados. Genera 100,000 enteros aleatorios de 0 a 99 y rastrea la instancia de cada entero.
Lo primero que noto es que es más probable que obtenga un número bajo que uno alto. Lo ves más cuando
seed1
es alto yseed2
bajo. En un par de casos, obtuve solo 3 números.En el mejor de los casos, su algoritmo necesita un poco de refinamiento.
fuente
Si la
Math.Random()
función llama al sistema operativo para obtener la hora del día, entonces no puede compararla con su función. Su función es un PRNG, mientras que esa función se esfuerza por obtener números aleatorios reales. Manzanas y naranjas.Su PRNG puede ser rápido, pero no tiene suficiente información de estado para lograr un largo período antes de que se repita (y su lógica no es lo suficientemente sofisticada como para lograr los períodos posibles con tanta información de estado).
El período es la duración de la secuencia antes de que su PRNG comience a repetirse. Esto sucede tan pronto como la máquina PRNG realiza una transición de estado a un estado que es idéntico a algún estado pasado. A partir de ahí, repetirá las transiciones que comenzaron en ese estado. Otro problema con los PRNG puede ser un bajo número de secuencias únicas, así como una convergencia degenerada en una secuencia particular que se repite. También puede haber patrones indeseables. Por ejemplo, suponga que un PRNG parece bastante aleatorio cuando los números se imprimen en decimal, pero una inspección de los valores en binario muestra que el bit 4 simplemente alterna entre 0 y 1 en cada llamada. ¡Uy!
Eche un vistazo al Mersenne Twister y otros algoritmos. Hay formas de lograr un equilibrio entre la duración del período y los ciclos de la CPU. Un enfoque básico (utilizado en el Mersenne Twister) es circular en el vector de estado. Es decir, cuando se genera un número, no se basa en todo el estado, solo en unas pocas palabras del conjunto de estados sujeto a algunas operaciones de bits. Pero en cada paso, el algoritmo también se mueve en la matriz, mezclando los contenidos poco a poco.
fuente
/dev/random
es una fuente de aleatoriedad real obtenida de los controladores de dispositivo, y no un PRNG. Bloquea cuando no hay suficientes bits disponibles. El dispositivo hermano/dev/urandom
tampoco bloquea, pero todavía no es exactamente un PRNG ya que se actualiza con bits aleatorios cuando están disponibles.nanoTime()
+ counter / hash se usan para la semilla predeterminadajava.util.Random
de oracle / OpenJDK. Eso es solo para la semilla, entonces es un LCG estándar. En efecto, el generador OP toma 2 números aleatorios para semilla, lo cual está bien, así que no hay diferencia quejava.util.Random
.System.currentTimeMillis()
fue la semilla predeterminada en JDK1.4-Hay muchos, muchos generadores de números pseudoaleatorios por ahí. Por ejemplo, el ranarray de Knuth , el tornado de Mersenne o buscar generadores LFSR. Los "algoritmos seminuméricos" monumentales de Knuth analizan el área y proponen algunos generadores congruenciales lineales (fáciles de implementar, rápidos).
Pero te sugiero que te limites a
java.util.Random
oMath.random
, son rápidos y al menos están bien para uso ocasional (es decir, juegos y demás). Si solo estás paranoico en la distribución (algún programa de Monte Carlo o un algoritmo genético), revisa su implementación (la fuente está disponible en algún lugar) y siembra con algún número verdaderamente aleatorio, ya sea de tu sistema operativo o de random.org . Si esto es necesario para alguna aplicación donde la seguridad es crítica, tendrá que cavar usted mismo. Y como en ese caso, no deberías creer lo que arroja un cuadrado de color con partes faltantes aquí, me callaré ahora.fuente
Es muy poco probable que el rendimiento de la generación de números aleatorios sea un problema para cualquier caso de uso que se le ocurra, a menos que acceda a una sola
Random
instancia desde varios subprocesos (porque loRandom
essynchronized
).Sin embargo, si ese es realmente el caso y necesita muchos números aleatorios rápidamente, su solución es demasiado poco confiable. A veces da buenos resultados, a veces da resultados horribles (según la configuración inicial).
Si desea los mismos números que
Random
le da la clase, solo que más rápido, puede deshacerse de la sincronización allí:Simplemente tomé el
java.util.Random
código y eliminé la sincronización que da como resultado el doble de rendimiento en comparación con el original en mi Oracle HotSpot JVM 7u9. Todavía es más lento que tuQuickRandom
, pero da resultados mucho más consistentes. Para ser precisos, para los mismosseed
valores y aplicaciones de subproceso único, proporciona los mismos números pseudoaleatorios que laRandom
clase original .Este código se basa en el actual
java.util.Random
en OpenJDK 7u, que está licenciado bajo GNU GPL v2 .EDITAR 10 meses después:
Acabo de descubrir que ni siquiera tiene que usar mi código anterior para obtener una
Random
instancia no sincronizada . ¡También hay uno en el JDK!Mira la
ThreadLocalRandom
clase de Java 7 . El código dentro de él es casi idéntico a mi código anterior. La clase es simplemente unaRandom
versión aislada de hilo local adecuada para generar números aleatorios rápidamente. El único inconveniente que se me ocurre es que no puede configurarloseed
manualmente.Ejemplo de uso:
fuente
:)
Eso es interesante, gracias!'Aleatorio' es más que solo obtener números ... lo que tienes es pseudoaleatorio
Si seudoaleatorio es lo suficientemente bueno para sus propósitos, entonces seguro, es mucho más rápido (y XOR + Bitshift será más rápido de lo que tiene)
Rolf
Editar:
OK, después de ser demasiado apresurado en esta respuesta, déjame responder la verdadera razón por la cual tu código es más rápido:
Desde JavaDoc para Math.Random ()
Esto es probable por qué su código es más rápido.
fuente
Math.random()
es más lenta. Tendría que sincronizar o crear uno nuevoRandom
cada vez, y ninguno de los dos es muy atractivo en términos de rendimiento. Si me importara el rendimiento, crearía el míonew Random
y lo usaría. : Pjava.util.Random no es muy diferente, un LCG básico descrito por Knuth. Sin embargo, tiene las 2 principales ventajas / diferencias principales:
Debajo está la rutina principal que genera enteros 'aleatorios' en java.util.Random.
Si elimina el AtomicLong y el estado no revelado (es decir, utilizando todos los bits del
long
), obtendría más rendimiento que la doble multiplicación / módulo.Última nota:
Math.random
no debe usarse para nada más que pruebas simples, es propenso a disputas y si tiene incluso un par de hilos que lo llaman simultáneamente, el rendimiento se degrada. Una característica histórica poco conocida es la introducción de CAS en Java: para superar un punto de referencia infame (primero por IBM a través de intrínsecos y luego Sun hizo "CAS de Java")fuente
Esta es la función aleatoria que uso para mis juegos. Es bastante rápido y tiene una buena distribución (suficiente).
fuente
volatile
el compilador es libre de eliminar (o introducir) las variables locales a voluntad.