Java: número largo aleatorio en 0 <= x <n rango

135

La clase aleatoria tiene un método para generar int aleatorio en un rango dado. Por ejemplo:

Random r = new Random(); 
int x = r.nextInt(100);

Esto generaría un número int más o igual a 0 y menos de 100. Me gustaría hacer exactamente lo mismo con un número largo.

long y = magicRandomLongGenerator(100);

La clase aleatoria solo tiene nextLong (), pero no permite establecer el rango.

Vilius Normantas
fuente
Relacionado, puede ser útil: stackoverflow.com/questions/2290057/…
TJ Crowder
1
¿Has considerado simplemente obtener tu largo aleatorio y tomar el mod de tu rango? (Por supuesto, si el rango es solo 100, produciría un int aleatorio y lo lanzaría demasiado).
Hot Licks
java.util.Randomsolo usa una distribución de 48 bits (ver detalles de implementación), por lo que no tendrá una distribución normal.
Geoffrey De Smet
1
En los tiempos modernos, uno podría considerar usar org.apache.commons.lang3.RandomUtils # nextLong.
reallynice

Respuestas:

149

A partir de Java 7 (o Android API Nivel 21 = 5.0+) puede usar directamente ThreadLocalRandom.current().nextLong(n)(para 0 ≤ x <n) y ThreadLocalRandom.current().nextLong(m, n)(para m ≤ x <n). Vea la respuesta de @Alex para más detalles.


Si está atascado con Java 6 (o Android 4.x) necesita usar una biblioteca externa (por ejemplo org.apache.commons.math3.random.RandomDataGenerator.getRandomGenerator().nextLong(0, n-1), vea la respuesta de @mawaldne ), o implementar la suya propia nextLong(n).

De acuerdo con https://docs.oracle.com/javase/1.5.0/docs/api/java/util/Random.html nextInt se implementa como

 public int nextInt(int n) {
     if (n<=0)
                throw new IllegalArgumentException("n must be positive");

     if ((n & -n) == n)  // i.e., n is a power of 2
         return (int)((n * (long)next(31)) >> 31);

     int bits, val;
     do {
         bits = next(31);
         val = bits % n;
     } while(bits - val + (n-1) < 0);
     return val;
 }

Entonces podemos modificar esto para realizar nextLong:

long nextLong(Random rng, long n) {
   // error checking and 2^x checking removed for simplicity.
   long bits, val;
   do {
      bits = (rng.nextLong() << 1) >>> 1;
      val = bits % n;
   } while (bits-val+(n-1) < 0L);
   return val;
}
kennytm
fuente
1
Tengo algunos problemas con la parte "2 ^ x comprobación". ¿Algunas ideas?
Vilius Normantas
@Vilius: la comprobación 2 ^ x solo hace que la generación sea más rápida porque el uso directo rng.nextLong() % ndará valores uniformes (suponga que todos los bits son buenos). Puedes ignorar esa parte si quieres.
kennytm
Si quiero m <= x <= n, ¿cómo modificarías tu solución?
BJ Peter DeLaCruz
66
@BJPeterDeLaCruz: un número aleatorio entre my nse puede obtener con un número aleatorio entre 0y n-m, a continuación, añadir m.
kennytm
84

ThreadLocalRandom

ThreadLocalRandomTiene un nextLong(long bound)método.

long v = ThreadLocalRandom.current().nextLong(100);

También tiene nextLong(long origin, long bound)si necesita un origen distinto de 0. Pase el origen (inclusive) y el límite (exclusivo).

long v = ThreadLocalRandom.current().nextLong(10,100); // For 2-digit integers, 10-99 inclusive.

SplittableRandomtiene los mismos nextLongmétodos y le permite elegir una semilla si desea una secuencia de números reproducible.

Alex - GlassEditor.com
fuente
55
Esta respuesta es mucho más simple y, por lo tanto, más útil que la más votada.
yurin
2
Para aquellos que desarrollan para Android, tenga en cuenta que solo está disponible en API 21 (Lollipop, Android 5.0): developer.android.com/reference/java/util/concurrent/…
desarrollador de Android
75

El método estándar para generar un número (sin un método de utilidad) en un rango es usar el doble con el rango:

long range = 1234567L;
Random r = new Random()
long number = (long)(r.nextDouble()*range);

le dará un largo entre 0 (inclusive) y rango (exclusivo). Del mismo modo, si desea un número entre x e y:

long x = 1234567L;
long y = 23456789L;
Random r = new Random()
long number = x+((long)(r.nextDouble()*(y-x)));

le dará un largo desde 1234567 (inclusive) hasta 123456789 (exclusivo)

Nota: compruebe los paréntesis, porque la conversión a larga tiene mayor prioridad que la multiplicación.

M. Jessup
fuente
55
Mi primera idea fue exactamente esto. Pero parece ser un poco poco elegante. Y estoy preocupado por la uniformidad de la distribución (no es que realmente lo necesite, solo quiero hacerlo bien)
Vilius Normantas
66
Por favor, nunca uses esto. La salida no es uniforme en absoluto.
Navin
2
El mayor problema es que el redondeo hará que el bit más bajo sea muy no uniforme. Además, boundtendrá que ser menor que el entero más grande que se puede codificar en un doble, 2 ^ 53.
Aleksandr Dubinsky
12

Los métodos anteriores funcionan muy bien. Si está usando apache commons (org.apache.commons.math.random), consulte RandomData. Tiene un método: nextLong (largo inferior, largo superior)

http://commons.apache.org/math/userguide/random.html

http://commons.apache.org/math/api-1.1/org/apache/commons/math/random/RandomData.html#nextLong(long,%20long)

mawaldne
fuente
3
Para la posteridad: RandomData está en desuso en 4.0. Use commons.apache.org/proper/commons-math/apidocs/org/apache/…
Michael Tontchev
11

Use el operador '%'

resultingNumber = (r.nextLong() % (maximum - minimum)) + minimum;

Al usar el operador '%', tomamos el resto cuando se divide entre su valor máximo. Esto nos deja con solo números desde 0 (inclusive) hasta el divisor (exclusivo).

Por ejemplo:

public long randLong(long min, long max) {
    return (new java.util.Random().nextLong() % (max - min)) + min;
}
Desbordamiento séptico
fuente
Esto es bueno, pero usted debe comprobarif (max == min)
khcpietro
Y también verifiqueif (nextLong() >= 0)
khcpietro
66
FYI: Esto no siempre da una distribución uniforme, y es realmente malo para algunos rangos grandes. Por ejemplo, si min = 0y max = 2 * (MAX_LONG / 3), entonces tiene el doble de probabilidades de obtener un valor [0, MAX_LONG / 3]que de obtener uno [MAX_LONG / 3, 2 * (MAX_LONG / 3)].
Nick
Este código no funcionará. si nextLongdevuelve un valor negativo, el resto será negativo y el valor estará fuera del rango.
Arnaud
3

Mejorando aún más la respuesta de kennytm: una implementación de subclase que tenga en cuenta la implementación real en Java 8 sería:

public class MyRandom extends Random {
  public long nextLong(long bound) {
    if (bound <= 0) {
      throw new IllegalArgumentException("bound must be positive");
    }

    long r = nextLong() & Long.MAX_VALUE;
    long m = bound - 1L;
    if ((bound & m) == 0) { // i.e., bound is a power of 2
      r = (bound * r) >> (Long.SIZE - 1);
    } else {
      for (long u = r; u - (r = u % bound) + m < 0L; u = nextLong() & Long.MAX_VALUE);
    }
    return r;
  }
}
Enrice
fuente
Sé que esta es una respuesta antigua y es poco probable que se use, pero esta parte es demostrablemente incorrecta: if ((bound & m) == 0) { r = (bound * r) >> (Long.SIZE - 1); } Primero, es fácil demostrar con pruebas unitarias que esto realmente no produce números en el rango [0, límite]. En segundo lugar, es innecesariamente elaborado: r = r & mlograría el resultado deseado, y eso es básicamente lo que hace la implementación actual de Java 8. Es posible que la implementación fuera diferente cuando se escribió esta respuesta, pero no pudo haber sido lo que se muestra.
E. Bishop
3

Si desea un pseudoaleatorio distribuido uniformemente largo en el rango de [0, m), intente usar el operador de módulo y el método de valor absoluto combinado con el nextLong()método como se ve a continuación:

Math.abs(rand.nextLong()) % m;

¿Dónde randestá tu objeto aleatorio?

El operador de módulo divide dos números y genera el resto de esos números. Por ejemplo, 3 % 2es 1porque el resto de 3 y 2 es 1.

Como nextLong()genera una pseudoaleatoria distribuida uniformemente en el rango de [- (2 ^ 48), 2 ^ 48) (o en algún lugar de ese rango), deberá tomar el valor absoluto de la misma. Si no lo hace, el módulo del nextLong()método tiene una probabilidad del 50% de devolver un valor negativo, que está fuera del rango [0, m).

Lo que solicitó inicialmente fue un pseudoaleatorio distribuido uniformemente de largo en el rango de [0,100). El siguiente código lo hace:

Math.abs(rand.nextLong()) % 100;
TheGamePlayer 40
fuente
1
módulo está sesgado, no lo use para stackoverflow
Sirenas
2

Qué tal esto:

public static long nextLong(@NonNull Random r, long min, long max) {
    if (min > max)
        throw new IllegalArgumentException("min>max");
    if (min == max)
        return min;
    long n = r.nextLong();
    //abs (use instead of Math.abs, which might return min value) :
    n = n == Long.MIN_VALUE ? 0 : n < 0 ? -n : n;
    //limit to range:
    n = n % (max - min);
    return min + n;
}

?

desarrollador de Android
fuente
Está bien, excepto las partes que pertenecen a un marco (supongo).
Damir Olejar
2

El siguiente método le devolverá un valor entre 10000000000 y 9999999999

long min = 1000000000L
long max = 9999999999L    

public static long getRandomNumber(long min, long max){

    Random random = new Random();         
    return random.nextLong() % (max - min) + max;

}
Arpan Saini
fuente
Cuando reinicio largo min = 1L; largo max = 10L; ¡El número aleatorio resultante va más allá del valor máximo!
Raj Rajen
Debe ser random.nextLong ()% (max - min) + min;
Jay Jodiwal
2

Desde Java 8 API

Podría ser más fácil tomar la implementación real del documento API https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long- lo están utilizando para generar longs stream. Y su origen puede ser "0" como en la pregunta.

long nextLong(long origin, long bound) {
  long r = nextLong();
  long n = bound - origin, m = n - 1;
  if ((n & m) == 0L)  // power of two
    r = (r & m) + origin;
  else if (n > 0L) {  // reject over-represented candidates
    for (long u = r >>> 1;            // ensure nonnegative
         u + m - (r = u % n) < 0L;    // rejection check
         u = nextLong() >>> 1) // retry
        ;
    r += origin;
  }
  else {              // range not representable as long
    while (r < origin || r >= bound)
      r = nextLong();
  }
  return r;
}
Vital
fuente
1

De la página al azar :

El método nextLong lo implementa la clase Random como si:

public long nextLong() {
   return ((long)next(32) << 32) + next(32);
}

Debido a que la clase Random usa una semilla con solo 48 bits, este algoritmo no devolverá todos los valores largos posibles.

Entonces, si desea obtener un Long, ya no obtendrá el rango completo de 64 bits.

Sugeriría que si tienes un rango que se acerca a una potencia de 2, acumulas Longcomo en ese fragmento, así:

next(32) + ((long)nextInt(8) << 3)

para obtener un rango de 35 bits, por ejemplo.

Phil
fuente
2
Pero la documentación dice "Todos los 2 ^ 64 valores largos posibles se producen con (aproximadamente) la misma probabilidad". Entonces, aparentemente, el método nextLong () debería devolver todos los valores posibles. Por cierto, ¿cómo se relaciona la longitud de la semilla con la distribución de valores?
Vilius Normantas
0

Los métodos que usan r.nextDouble()deberían usar:

long number = (long) (rand.nextDouble()*max);


long number = x+(((long)r.nextDouble())*(y-x));
J bajo
fuente
0
public static long randomLong(long min, long max)
{
    try
    {
        Random  random  = new Random();
        long    result  = min + (long) (random.nextDouble() * (max - min));
        return  result;
    }
    catch (Throwable t) {t.printStackTrace();}
    return 0L;
}
XXX
fuente
1
No debe crear Randominstancias a medida, no debe detectar Throwableotras excepciones si no es necesario, debe registrar los errores con algún tipo de marco de registro (es decir, SLF4J) en lugar de usar printStackTrace.
Boguś
0

Si puede usar flujos de Java, puede intentar lo siguiente:

Random randomizeTimestamp = new Random();
Long min = ZonedDateTime.parse("2018-01-01T00:00:00.000Z").toInstant().toEpochMilli();
Long max = ZonedDateTime.parse("2019-01-01T00:00:00.000Z").toInstant().toEpochMilli();
randomizeTimestamp.longs(generatedEventListSize, min, max).forEach(timestamp -> {
  System.out.println(timestamp);
});

Esto generará números en el rango dado para largos.

Sridhar Sg
fuente
0
import java.util*;

    Random rnd = new Random ();
    long name = Math.abs(rnd.nextLong());

Esto debería funcionar

Liash101
fuente
-4

// usa el tiempo del sistema como valor semilla para obtener un buen número aleatorio

   Random random = new Random(System.currentTimeMillis());
              long x;
             do{
                x=random.nextLong();
             }while(x<0 && x > n); 

// Bucle hasta obtener un número mayor o igual a 0 y menor que n

Valentinos Ioannou
fuente
1
Esto puede ser extremadamente ineficiente. ¿Qué pasa si nes 1 o digo 2? El bucle realizará muchas iteraciones.
Magnilex