Java: nombre long aléatoire dans la plage 0 <= x <n


135

La classe aléatoire a une méthode pour générer des int aléatoires dans une plage donnée. Par exemple:

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

Cela générerait un nombre entier supérieur ou égal à 0 et inférieur à 100. J'aimerais faire exactement la même chose avec un nombre long.

long y = magicRandomLongGenerator(100);

La classe aléatoire n'a que nextLong (), mais elle ne permet pas de définir range.



1
Avez-vous envisagé de simplement obtenir votre long aléatoire et de prendre le mod de votre gamme? (Bien sûr, si la plage n'est que de 100, je produirais un int au hasard et le lancerais trop longtemps.)
Hot Licks

java.util.Randomutilise uniquement une distribution 48 bits (voir les détails de l'implémentation), donc elle n'aura pas de distribution normale.
Geoffrey De Smet

1
De nos jours, on pourrait envisager d'utiliser org.apache.commons.lang3.RandomUtils # nextLong.
reallynice le

Réponses:


149

À partir de Java 7 (ou Android API Level 21 = 5.0+), vous pouvez utiliser directement ThreadLocalRandom.current().nextLong(n)(pour 0 ≤ x <n) et ThreadLocalRandom.current().nextLong(m, n)(pour m ≤ x <n). Voir la réponse de @Alex pour plus de détails.


Si vous êtes bloqué avec Java 6 (ou Android 4.x), vous devez utiliser une bibliothèque externe (par exemple org.apache.commons.math3.random.RandomDataGenerator.getRandomGenerator().nextLong(0, n-1), voir la réponse de @mawaldne ), ou implémenter la vôtre nextLong(n).

Selon https://docs.oracle.com/javase/1.5.0/docs/api/java/util/Random.html nextInt est implémenté comme

 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;
 }

Nous pouvons donc modifier ceci pour effectuer 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;
}

1
J'ai des problèmes avec la partie "2 ^ x vérification". Des idées?
Vilius Normantas

@Vilius: La vérification 2 ^ x rend simplement la génération plus rapide car l'utilisation directe rng.nextLong() % ndonnera des valeurs uniformes (en supposant que tous les bits sont bons). Vous pouvez ignorer cette partie si vous le souhaitez.
kennytm

Si je le souhaite m <= x <= n, comment modifieriez-vous votre solution?
BJ Peter DeLaCruz

6
@BJPeterDeLaCruz: Un nombre aléatoire entre met npeut être obtenu avec un nombre aléatoire entre 0et n-m, puis additionnez m.
kennytm

84

ThreadLocalRandom

ThreadLocalRandoma une nextLong(long bound)méthode.

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

Il a également nextLong(long origin, long bound)si vous avez besoin d'une origine autre que 0. Passez l'origine (inclus) et la borne (exclusive).

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

SplittableRandoma les mêmes nextLongméthodes et vous permet de choisir une graine si vous voulez une séquence reproductible de nombres.


5
Cette réponse est bien plus simple et donc plus utile que la plus votée.
yurin

2
Pour ceux qui développent pour Android, notez qu'il est disponible uniquement à partir de l'API 21 (Lollipop, Android 5.0): developer.android.com/reference/java/util/concurrent/…
développeur Android

75

La méthode standard pour générer un nombre (sans méthode utilitaire) dans une plage consiste simplement à utiliser le double avec la plage:

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

vous donnera un long entre 0 (inclus) et intervalle (exclusif). De même si vous voulez un nombre entre x et y:

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

vous donnera un long de 1234567 (inclus) à 123456789 (exclusif)

Remarque: vérifiez les parenthèses, car la conversion en long a une priorité plus élevée que la multiplication.


5
Ma première idée était exactement celle-ci. Mais cela semble un peu inélégant. Et je m'inquiète de l'uniformité de la distribution (ce n'est pas que j'en ai vraiment besoin, je veux juste le faire correctement)
Vilius Normantas

6
Merci de ne jamais l'utiliser. La sortie n'est pas du tout uniforme.
Navin

2
Le plus gros problème est que l'arrondi rendra le bit le plus bas très non uniforme. En outre, bounddevra être inférieur au plus grand entier pouvant être encodé dans un double, 2 ^ 53.
Aleksandr Dubinsky

12

Les méthodes ci-dessus fonctionnent très bien. Si vous utilisez apache commons (org.apache.commons.math.random) consultez RandomData. Il a une méthode: nextLong (long inférieur, long supérieur)

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)


3
Pour la postérité: RandomData est obsolète dans la version 4.0. Utilisez commons.apache.org/proper/commons-math/apidocs/org/apache/…
Michael Tontchev

11

Utilisez l'opérateur '%'

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

En utilisant l'opérateur '%', nous prenons le reste lorsqu'il est divisé par votre valeur maximale. Cela nous laisse avec seulement des nombres de 0 (inclus) au diviseur (exclusif).

Par exemple:

public long randLong(long min, long max) {
    return (new java.util.Random().nextLong() % (max - min)) + min;
}

C'est bien, mais vous devriez vérifierif (max == min)
khcpietro

Et aussi vérifierif (nextLong() >= 0)
khcpietro

6
FYI: Cela ne donne pas toujours une distribution uniforme, et c'est vraiment mauvais pour certaines grandes plages. Par exemple, si min = 0et max = 2 * (MAX_LONG / 3), alors vous êtes deux fois plus susceptible d'obtenir une valeur [0, MAX_LONG / 3]que vous l'êtes [MAX_LONG / 3, 2 * (MAX_LONG / 3)].
Nick

Ce code ne fonctionnera pas. si nextLongrenvoie une valeur négative, le reste sera négatif et la valeur sera en dehors de la plage.
Arnaud

3

Améliorer encore la réponse de kennytm: Une implémentation de sous-classe prenant en compte l'implémentation réelle dans Java 8 serait:

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;
  }
}

Je sais que c'est une vieille réponse et peu susceptible d'être utilisée, mais cette partie est manifestement incorrecte: if ((bound & m) == 0) { r = (bound * r) >> (Long.SIZE - 1); } Premièrement, il est facile de montrer avec des tests unitaires que cela ne produit pas réellement de nombres dans la plage [0, lié). Deuxièmement, c'est inutilement élaboré: cela r = r & mpermettrait d'obtenir le résultat souhaité, et c'est essentiellement ce que fait l'implémentation actuelle de Java 8. Il est possible que l'implémentation ait été différente lorsque cette réponse a été écrite, mais cela ne peut pas avoir été ce qui est montré.
E. Bishop

3

Si vous voulez un long pseudo-aléatoire uniformément distribué dans la plage de [0, m), essayez d'utiliser l'opérateur modulo et la méthode de la valeur absolue combinés avec la nextLong()méthode comme indiqué ci-dessous:

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

rand est votre objet aléatoire.

L'opérateur modulo divise deux nombres et génère le reste de ces nombres. Par exemple, 3 % 2est1 parce que le reste de 3 et 2 est 1.

Puisque nextLong()génère un long pseudo-aléatoire uniformément distribué dans la plage de [- (2 ^ 48), 2 ^ 48) (ou quelque part dans cette plage), vous devrez en prendre la valeur absolue. Sinon, le modulo de la nextLong()méthode a 50% de chances de renvoyer une valeur négative, qui est hors de la plage [0,m ).

Ce que vous avez initialement demandé était un long pseudo-aléatoire uniformément distribué dans la gamme de [0,100). Le code suivant le fait:

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

1
modulo est biaisé, ne l'utilisez pas pour stackoverflow.com/a/10984975/1166266
Sirens

2

Que dis-tu de ça:

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;
}

?


Tout va bien, sauf les parties qui appartiennent à un cadre (je suppose).
Damir Olejar

2

La méthode ci-dessous vous renverra une valeur comprise entre 10000000000 et 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;

}

Quand je réinitialise long min = 1L; long max = 10L; Le nombre aléatoire résultant dépasse la valeur maximale!
Raj Rajen

Il doit être random.nextLong ()% (max - min) + min;
Jay Jodiwal

2

Depuis Java 8 API

Il pourrait être plus facile de prendre la mise en œuvre réelle à partir de la documentation API https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long- ils l'utilisent pour générer un flux long. Et votre origine peut être "0" comme dans la question.

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;
}

1

À partir de la page sur Random :

La méthode nextLong est implémentée par la classe Random comme si par:

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

Étant donné que la classe Random utilise une valeur de départ avec seulement 48 bits, cet algorithme ne retournera pas toutes les valeurs longues possibles.

Donc, si vous voulez obtenir un Long , vous déjà pas la gamme complète de 64 bits.

Je suggérerais que si vous avez une portée proche d'une puissance de 2, vous construisez le Longcomme dans cet extrait de code, comme ceci:

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

pour obtenir une plage de 35 bits, par exemple.


2
Mais la documentation dit: "Les 2 ^ 64 valeurs longues possibles sont produites avec une probabilité (approximativement) égale." Donc, apparemment, la méthode nextLong () devrait retourner toutes les valeurs possibles. Btw, comment la longueur de la graine est liée à la distribution des valeurs?
Vilius Normantas

0

Les méthodes utilisant le r.nextDouble()devraient utiliser:

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


long number = x+(((long)r.nextDouble())*(y-x));

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;
}

1
Vous ne devriez pas créer d' Randominstances à coup sûr, vous ne devriez pas attraper des Throwableexceptions ou d'autres exceptions si cela n'est pas nécessaire, vous devriez enregistrer les erreurs avec une sorte de cadre de journalisation (c'est-à-dire SLF4J) au lieu d'utiliser printStackTrace.
Boguś

0

Si vous pouvez utiliser des flux Java, vous pouvez essayer ce qui suit:

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);
});

Cela générera des nombres dans la plage donnée pour les longs.


0
import java.util*;

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

Cela devrait fonctionner


-4

// utilise l'heure système comme valeur de départ pour obtenir un bon nombre aléatoire

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

// Boucle jusqu'à obtenir un nombre supérieur ou égal à 0 et inférieur à n


1
Cela peut être extrêmement inefficace. Et si nvaut 1 ou disons 2? La boucle effectuera de nombreuses itérations.
Magnilex
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.