securerandom number ints generate examples example create and java performance algorithm random

number - random java examples



¿Es este un algoritmo aleatorio "suficientemente bueno"? ¿Por qué no se usa si es más rápido? (14)

El generador aleatorio más rápido que puede implementar es este:

XD, bromas aparte, además de todo lo dicho 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, puedes encontrar un muchos de ellos aquí: http://www.random.org/analysis/#2005

Una forma simple de evaluar la "calidad" aleatoria del generador es la vieja prueba de Chi Square.

static double chisquare(int numberCount, int maxRandomNumber) { long[] f = new long[maxRandomNumber]; for (long i = 0; i < numberCount; i++) { f[randomint(maxRandomNumber)]++; } long t = 0; for (int i = 0; i < maxRandomNumber; i++) { t += f[i] * f[i]; } return (((double) maxRandomNumber * t) / numberCount) - (double) (numberCount); }

Citando [1]

La idea de la prueba de χ² es verificar si los números producidos se distribuyen razonablemente o no. Si generamos N números positivos menores que r , entonces esperaríamos obtener aproximadamente N / r números de cada valor. Pero --- y esta es la esencia del asunto --- las frecuencias de ocurrencia de todos los valores no deberían ser exactamente las mismas: ¡eso no sería aleatorio!

Simplemente calculamos la suma de los cuadrados de las frecuencias de ocurrencia de cada valor, escalado por la frecuencia esperada, y luego restamos el tamaño de la secuencia. Este número, la "estadística χ²," se puede expresar matemáticamente como

Si la estadística χ² está cerca de r , entonces los números son aleatorios; si está demasiado lejos, entonces no lo son. Las nociones de "cerca" y "muy lejos" se pueden definir con mayor precisión: existen tablas que dicen exactamente cómo se relaciona la estadística con las propiedades de las secuencias aleatorias. Para la prueba simple que estamos realizando, la estadística debe estar dentro de 2√r

Usando esta teoría y el siguiente código:

abstract class RandomFunction { public abstract int randomint(int range); } public class test { static QuickRandom qr = new QuickRandom(); static double chisquare(int numberCount, int maxRandomNumber, RandomFunction function) { long[] f = new long[maxRandomNumber]; for (long i = 0; i < numberCount; i++) { f[function.randomint(maxRandomNumber)]++; } long t = 0; for (int i = 0; i < maxRandomNumber; i++) { t += f[i] * f[i]; } return (((double) maxRandomNumber * t) / numberCount) - (double) (numberCount); } public static void main(String[] args) { final int ITERATION_COUNT = 1000; final int N = 5000000; final int R = 100000; double total = 0.0; RandomFunction qrRandomInt = new RandomFunction() { @Override public int randomint(int range) { return (int) (qr.random() * range); } }; for (int i = 0; i < ITERATION_COUNT; i++) { total += chisquare(N, R, qrRandomInt); } System.out.printf("Ave Chi2 for QR: %f /n", total / ITERATION_COUNT); total = 0.0; RandomFunction mathRandomInt = new RandomFunction() { @Override public int randomint(int range) { return (int) (Math.random() * range); } }; for (int i = 0; i < ITERATION_COUNT; i++) { total += chisquare(N, R, mathRandomInt); } System.out.printf("Ave Chi2 for Math.random: %f /n", total / ITERATION_COUNT); } }

Obtuve el siguiente resultado:

Ave Chi2 for QR: 108965,078640 Ave Chi2 for Math.random: 99988,629040

Que, para QuickRandom, está lejos de r (fuera de r ± 2 * sqrt(r) )

Dicho esto, QuickRandom podría ser rápido pero (como se afirma 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

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 un double y toma la parte decimal.

Aquí está mi clase QuickRandom 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 doble de "número mágico". Lo lancé bastante rápido, así que probablemente podría hacerlo mejor, pero extrañamente, parece estar funcionando bien.

Este es un resultado de muestra de las líneas comentadas en el método main :

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í hay un ejemplo de salida 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 alguna parte que Math.random usaba System.nanoTime() y toneladas de módulos locos 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" (por ejemplo, para un juego en el que los números aleatorios no son demasiado importantes)?
  • ¿Por qué Math.random hace tanto cuando parece que la simple multiplicación y el recorte del decimal serán suficientes?

El verdadero problema con esto es que su histograma de salida depende mucho de la semilla inicial; la mayor parte del tiempo terminará con una producción casi uniforme, pero muchas veces tendrá un resultado claramente desigual.

Inspirado por este artículo sobre cuán mala es la función de rand() php , hice algunas imágenes de matrices aleatorias usando QuickRandom y System.Random . Esta ejecución muestra cómo a veces la semilla puede tener un mal efecto (en este caso favoreciendo números más bajos) mientras que System.Random es bastante uniforme.

QuickRandom

System.Random

Peor aún

Si iniciamos QuickRandom como new QuickRandom(0.01, 1.03) obtenemos esta imagen:

El código

using System; using System.Drawing; using System.Drawing.Imaging; namespace QuickRandomTest { public class QuickRandom { private double prevNum; private readonly double magicNumber; private static readonly Random rand = new Random(); public QuickRandom(double seed1, double seed2) { if (seed1 >= 1 || seed1 < 0) throw new ArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1); prevNum = seed1; if (seed2 <= 1 || seed2 > 10) throw new ArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2); magicNumber = seed2; } public QuickRandom() : this(rand.NextDouble(), rand.NextDouble() * 10) { } public double Random() { return prevNum = (prevNum * magicNumber) % 1; } } class Program { static void Main(string[] args) { var rand = new Random(); var qrand = new QuickRandom(); int w = 600; int h = 600; CreateMatrix(w, h, rand.NextDouble).Save("System.Random.png", ImageFormat.Png); CreateMatrix(w, h, qrand.Random).Save("QuickRandom.png", ImageFormat.Png); } private static Image CreateMatrix(int width, int height, Func<double> f) { var bitmap = new Bitmap(width, height); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { var c = (int) (f()*255); bitmap.SetPixel(x, y, Color.FromArgb(c,c,c)); } } return bitmap; } } }


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 única instancia Random desde múltiples hilos (porque Random está synchronized ).

Sin embargo, si ese es realmente el caso y necesita muchos números aleatorios rápidamente, su solución es demasiado poco fiable. A veces da buenos resultados, a veces da resultados horribles (según la configuración inicial).

Si quieres los mismos números que te da la clase Random , solo que más rápido, puedes deshacerte de la sincronización allí:

public class QuickRandom { private long seed; private static final long MULTIPLIER = 0x5DEECE66DL; private static final long ADDEND = 0xBL; private static final long MASK = (1L << 48) - 1; public QuickRandom() { this((8682522807148012L * 181783497276652981L) ^ System.nanoTime()); } public QuickRandom(long seed) { this.seed = (seed ^ MULTIPLIER) & MASK; } public double nextDouble() { return (((long)(next(26)) << 27) + next(27)) / (double)(1L << 53); } private int next(int bits) { seed = (seed * MULTIPLIER + ADDEND) & MASK; return (int)(seed >>> (48 - bits)); } }

Simplemente tomé el código java.util.Random y eliminé la sincronización que resulta en el doble del rendimiento en comparación con el original en mi Oracle HotSpot JVM 7u9. Todavía es más lento que su QuickRandom , pero ofrece resultados mucho más consistentes. Para ser precisos, para los mismos valores de seed y las mismas aplicaciones de subproceso único, proporciona los mismos números pseudoaleatorios que la clase Random original.

Este código se basa en el java.util.Random actual java.util.Random que está licenciado bajo GNU GPL v2 .

EDIT 10 meses después:

Acabo de descubrir que ni siquiera tiene que usar mi código anterior para obtener una instancia Random no sincronizada. ¡También hay uno en el JDK!

Mire la clase ThreadLocalRandom Java 7. El código dentro de él es casi idéntico a mi código anterior. La clase es simplemente una versión Random hilo local aislada, adecuada para generar números aleatorios rápidamente. El único inconveniente que puedo pensar es que no puedes establecer su seed manualmente.

Ejemplo de uso:

Random random = ThreadLocalRandom.current();


Hay muchos, muchos generadores de números pseudo aleatorios por ahí. Por ejemplo, el ranarray de Knuth, el tornado de Mersenne , o buscar generadores LFSR. Los monumentales "algoritmos de Seminumerical" de Knuth analizan el área y proponen algunos generadores congruenciales lineales (fáciles de implementar, rápidos).

Pero te sugiero que simplemente te Math.random a java.util.Random o Math.random , que sean rápidos y al menos aceptables para el uso ocasional (es decir, juegos y Math.random ). Si solo está paranoico con la distribución (algún programa de Monte Carlo o un algoritmo genético), revise su implementación (la fuente está disponible en algún lugar) y siembre con algún número verdaderamente aleatorio, ya sea desde su sistema operativo o desde 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 es un cuadrado de color con picos que faltan aquí, me callaré ahora.


Junté una maqueta rápida de tu 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 un número alto. Ves esto más cuando seed1 es alto y seed2 es bajo. En un par de instancias, obtuve solo 3 números.

En el mejor de los casos, su algoritmo necesita un refinamiento.


Lo que está describiendo es un tipo de generador aleatorio llamado generador congruente lineal . El generador funciona de la siguiente manera:

  • Comience con un valor inicial y un multiplicador.
  • Para generar un número aleatorio:
    • Multiplica la semilla por el multiplicador.
    • Establezca la semilla igual a este valor.
    • Devuelve este valor

Este generador tiene muchas propiedades agradables, pero tiene problemas importantes 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 buen enfoque.

¡Espero que esto ayude!


Si la función Math.Random() 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 busca números aleatorios reales. Manzanas y naranjas.

Su PRNG puede ser rápido, pero no tiene suficiente información de estado para lograr un período prolongado antes de que se repita (y su lógica no es lo suficientemente sofisticada como para alcanzar los períodos que son 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 hace una transición de estado a un estado que es idéntico a un 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, supongamos 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 está alternando entre 0 y 1 en cada llamada. Oops!

Eche un vistazo a 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 Mersenne Twister) es recorrer el vector de estado. Es decir, cuando se genera un número, no se basa en todo el estado, solo en unas pocas palabras de la matriz de estado sujeta a algunas operaciones de bits. Pero en cada paso, el algoritmo también se mueve en la matriz, mezclando los contenidos un poco a la vez.


Su función de número aleatorio es pobre, ya que tiene muy poco estado interno: el número que emite la función en cualquier paso dado depende por completo del número anterior. Por ejemplo, si suponemos que magicNumber es 2 (a modo de ejemplo), entonces la secuencia:

0.10 -> 0.20

está fuertemente reflejado por secuencias similares:

0.09 -> 0.18 0.11 -> 0.22

En muchos casos, esto generará notables correlaciones en su juego; por ejemplo, si realiza llamadas sucesivas a su función para generar coordenadas X e Y para objetos, los objetos formarán patrones diagonales claros.

A menos que tenga buenas razones para creer que el generador de números aleatorios está desacelerando su aplicación (y esto es MUY improbable), no hay una buena razón para intentar escribir la suya propia.


Su implementación de QuickRandom realmente no tiene una distribución uniforme. Las frecuencias son generalmente más altas en los valores más bajos, mientras que Math.random() tiene una distribución más uniforme. Aquí hay un SSCCE que muestra que:

package com..q14491966; import java.util.Arrays; public class Test { public static void main(String[] args) throws Exception { QuickRandom qr = new QuickRandom(); int[] frequencies = new int[10]; for (int i = 0; i < 100000; i++) { frequencies[(int) (qr.random() * 10)]++; } printDistribution("QR", frequencies); frequencies = new int[10]; for (int i = 0; i < 100000; i++) { frequencies[(int) (Math.random() * 10)]++; } printDistribution("MR", frequencies); } public static void printDistribution(String name, int[] frequencies) { System.out.printf("%n%s distribution |8000 |9000 |10000 |11000 |12000%n", name); for (int i = 0; i < 10; i++) { char[] bar = " ".toCharArray(); // 50 chars. Arrays.fill(bar, 0, Math.max(0, Math.min(50, frequencies[i] / 100 - 80)), ''#''); System.out.printf("0.%dxxx: %6d :%s%n", i, frequencies[i], new String(bar)); } } }

El resultado promedio se ve así:

QR distribution |8000 |9000 |10000 |11000 |12000 0.0xxx: 11376 :################################# 0.1xxx: 11178 :############################### 0.2xxx: 11312 :################################# 0.3xxx: 10809 :############################ 0.4xxx: 10242 :###################### 0.5xxx: 8860 :######## 0.6xxx: 9004 :########## 0.7xxx: 8987 :######### 0.8xxx: 9075 :########## 0.9xxx: 9157 :########### MR distribution |8000 |9000 |10000 |11000 |12000 0.0xxx: 10097 :#################### 0.1xxx: 9901 :################### 0.2xxx: 10018 :#################### 0.3xxx: 9956 :################### 0.4xxx: 9974 :################### 0.5xxx: 10007 :#################### 0.6xxx: 10136 :##################### 0.7xxx: 9937 :################### 0.8xxx: 10029 :#################### 0.9xxx: 9945 :###################

Si repites la prueba, verás que la distribución de QR varía mucho, dependiendo de las semillas iniciales, mientras que la distribución de MR es estable. A veces alcanza la distribución uniforme deseada, pero más que a menudo no lo hace. Este es uno de los ejemplos más extremos, incluso está más allá de los límites del gráfico:

QR distribution |8000 |9000 |10000 |11000 |12000 0.0xxx: 41788 :################################################## 0.1xxx: 17495 :################################################## 0.2xxx: 10285 :###################### 0.3xxx: 7273 : 0.4xxx: 5643 : 0.5xxx: 4608 : 0.6xxx: 3907 : 0.7xxx: 3350 : 0.8xxx: 2999 : 0.9xxx: 2652 :


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 envíe hasta el final de los tiempos, ya que solo hay uno posible próximo resultado, y así sucesivamente 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 eso es en el mejor de los casos: ¿puedes demostrar que no hay bucles del período 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 lo hace, entonces su RNG estará sesgado, o peor, sesgado de diferentes maneras dependiendo de la semilla inicial.

Si puedes responder todas estas preguntas, increíble. 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 más rápido al azar () en el mundo, pero no es bueno si no es muy aleatorio)


Una prueba común que siempre hice al desarrollar PRNGs fue:

  1. Convertir la salida a valores de char
  2. Escribir valor de caracteres en un archivo
  3. Comprimir archivo

Esto me permitió repetir rápidamente las ideas que eran "suficientemente buenas" PRNG para secuencias de alrededor de 1 a 20 megabytes. También dio una mejor imagen de arriba hacia abajo que solo inspeccionarla a simple vista, ya que cualquier PRNG "suficientemente bueno" con media palabra de estado podría exceder rápidamente la capacidad de tus ojos de ver el punto del ciclo.

Si fuera realmente quisquilloso, podría tomar los buenos algoritmos y ejecutar las pruebas DIEHARD / NIST en ellos, para obtener más información, y luego volver 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 muestre un bloque de 256 longitudes que contenga todos los caracteres de 0 a 255, y hágalo 100.000 veces. Pero esta secuencia tiene un ciclo de longitud 256.

Una distribución sesgada, incluso por un pequeño margen, debe ser captada por un algoritmo de compresión, particularmente si le da suficiente (digamos 1 megabyte) de la secuencia para trabajar. Si algunos caracteres, o bigramas, o n-gramas ocurren con mayor frecuencia, un algoritmo de compresión puede codificar esta distribución oblicua a códigos que favorecen las ocurrencias frecuentes con palabras de código más cortas, y obtienes 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 simplemente por ahí), la prueba de compresión es muy útil para calificar rápidamente el pase / falla de un PRNG que pueda estar desarrollando.

Buena suerte con tus experimentos!

Oh, realicé esta prueba en el rng que tienes arriba, usando la siguiente pequeña modificación de tu código:

import java.io.*; 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; } public static void main(String[] args) throws Exception { QuickRandom qr = new QuickRandom(); FileOutputStream fout = new FileOutputStream("qr20M.bin"); for (int i = 0; i < 20000000; i ++) { fout.write((char)(qr.random()*256)); } } }

Los resultados fueron:

Cris-Mac-Book-2:rt cris$ zip -9 qr20M.zip qr20M.bin2 adding: qr20M.bin2 (deflated 16%) Cris-Mac-Book-2:rt cris$ ls -al total 104400 drwxr-xr-x 8 cris staff 272 Jan 25 05:09 . drwxr-xr-x+ 48 cris staff 1632 Jan 25 05:04 .. -rw-r--r-- 1 cris staff 1243 Jan 25 04:54 QuickRandom.class -rw-r--r-- 1 cris staff 883 Jan 25 05:04 QuickRandom.java -rw-r--r-- 1 cris staff 16717260 Jan 25 04:55 qr20M.bin.gz -rw-r--r-- 1 cris staff 20000000 Jan 25 05:07 qr20M.bin2 -rw-r--r-- 1 cris staff 16717402 Jan 25 05:09 qr20M.zip

Consideraría un PRNG bueno si el archivo de salida no pudiera comprimirse en absoluto. Para ser sincero, no pensé que tu PRNG fuera tan bueno, solo el 16% en ~ 20 Megas es bastante impresionante para una construcción tan simple. Pero todavía lo considero un fracaso.


''Random'' is more than just about getting numbers.... what you have is pseudo-random

If pseudo-random is good enough for your purposes, then sure, it''s way faster (and XOR+Bitshift will be faster than what you have)

Rolf

Editar:

OK, after being too hasty in this answer, let me answer the real reason why your code is faster:

From the JavaDoc for Math.Random()

This method is properly synchronized to allow correct use by more than one thread. However, if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator.

This is likely why your code is faster.


This is the random function I use for my games. It''s pretty fast, and has good (enough) distribution.

public class FastRandom { public static int randSeed; public static final int random() { // this makes a ''nod'' to being potentially called from multiple threads int seed = randSeed; seed *= 1103515245; seed += 12345; randSeed = seed; return seed; } public static final int random(int range) { return ((random()>>>15) * range) >>> 17; } public static final boolean randomBoolean() { return random() > 0; } public static final float randomFloat() { return (random()>>>8) * (1.f/(1<<24)); } public static final double randomDouble() { return (random()>>>8) * (1.0/(1<<24)); } }


java.util.Random is not much different, a basic LCG described by Knuth. However it has main 2 main advantages/differences:

  • thread safe - each update is a CAS which is more expensive than a simple write and needs a branch (even if perfectly predicted single threaded). Depending on the CPU it could be significant difference.
  • undisclosed internal state - this is very important for anything non-trivial. You wish the random numbers not to be predictable.

Below it''s the main routine generating ''random'' integers in java.util.Random.

protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); return (int)(nextseed >>> (48 - bits)); }

If you remove the AtomicLong and the undisclosed sate (ie using all bits of the long ), you''d get more performance than the double multiplication/modulo.

Last note: Math.random should not be used for anything but simple tests, it''s prone to contention and if you have even a couple of threads calling it concurrently the performance degrades. One little known historical feature of it is the introduction of CAS in java - to beat an infamous benchmark (first by IBM via intrinsics and then Sun made "CAS from Java")