Java: losowa długa liczba w zakresie 0 <= x <n

135

Klasa Random ma metodę generowania losowego int w podanym zakresie. Na przykład:

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

To wygenerowałoby liczbę int większą lub równą 0 i mniejszą niż 100. Dokładnie to samo chciałbym zrobić z liczbą długą.

long y = magicRandomLongGenerator(100);

Klasa losowa ma tylko nextLong (), ale nie pozwala na ustawienie zakresu.

Vilius Normantas
źródło
Powiązane, mogą się przydać: stackoverflow.com/questions/2290057/ ...
TJ Crowder
1
Czy zastanawiałeś się po prostu nad uzyskaniem długiego losowego i zmodyfikowaniem swojego zasięgu? (Oczywiście, jeśli zasięg wynosi tylko 100, wyprodukowałbym int losowy i rzucił go zbyt długo.)
Hot Licks
java.util.Randomużywa tylko dystrybucji 48-bitowej (zobacz szczegóły implementacji), więc nie będzie miała normalnej dystrybucji.
Geoffrey De Smet
1
W dzisiejszych czasach można by rozważyć użycie org.apache.commons.lang3.RandomUtils # nextLong.
naprawdę ładne

Odpowiedzi:

149

Począwszy od Java 7 (lub Android API Level 21 = 5.0+) możesz bezpośrednio użyć ThreadLocalRandom.current().nextLong(n)(dla 0 ≤ x <n) i ThreadLocalRandom.current().nextLong(m, n)(dla m ≤ x <n). Zobacz odpowiedź @Alex , aby uzyskać szczegółowe informacje.


Jeśli utkniesz z Javą 6 (lub Androidem 4.x), musisz skorzystać z zewnętrznej biblioteki (np. org.apache.commons.math3.random.RandomDataGenerator.getRandomGenerator().nextLong(0, n-1)Zobacz odpowiedź @mawaldne ) lub zaimplementować własną nextLong(n).

Według https://docs.oracle.com/javase/1.5.0/docs/api/java/util/Random.html nextInt jest zaimplementowany jako

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

Możemy więc zmodyfikować to, aby wykonać 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
źródło
1
Mam problemy z częścią „sprawdzanie 2 ^ x”. Jakieś pomysły?
Vilius Normantas
@Vilius: Sprawdzanie 2 ^ x tylko przyspiesza generowanie, ponieważ bezpośrednie użycie rng.nextLong() % nda jednolite wartości (załóżmy, że wszystkie bity są dobre). Jeśli chcesz, możesz zignorować tę część.
kennytm
Jeśli chcę m <= x <= n, jak zmodyfikowałbyś swoje rozwiązanie?
BJ Peter DeLaCruz
6
@BJPeterDeLaCruz: Liczba losowa pomiędzy moraz nmoże być uzyskane z wykorzystaniem liczby losowej między 0a n-m, a następnie dodać m.
kennytm
84

ThreadLocalRandom

ThreadLocalRandomma nextLong(long bound)metodę.

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

Ma również, nextLong(long origin, long bound)jeśli potrzebujesz innego źródła niż 0. Podaj początek (włącznie) i granicę (wyłączność).

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

SplittableRandomma te same nextLongmetody i pozwala wybrać ziarno, jeśli chcesz uzyskać powtarzalną sekwencję liczb.

Alex - GlassEditor.com
źródło
5
Ta odpowiedź jest znacznie prostsza, a zatem bardziej użyteczna niż ta, na którą najczęściej głosowano.
yurin
2
Dla tych, którzy rozwijają się na Androida, zauważ, że jest dostępny tylko z API 21 (Lollipop, Android 5.0): developer.android.com/reference/java/util/concurrent/ ...
programista Androida
75

Standardową metodą generowania liczby (bez metody narzędziowej) w zakresie jest po prostu użycie liczby podwójnej z zakresem:

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

da ci wartość długą od 0 (włącznie) do zakresu (wyłączność). Podobnie, jeśli chcesz liczbę między x a y:

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

daje długi od 1234567 (włącznie) do 123456789 (ekskluzywny)

Uwaga: sprawdź nawiasy, ponieważ rzutowanie na długie ma wyższy priorytet niż mnożenie.

M. Jessup
źródło
5
Mój pierwszy pomysł był dokładnie taki. Ale wydaje się to trochę nieeleganckie. I martwię się o jednolitość dystrybucji (nie chodzi o to, że naprawdę tego potrzebuję, po prostu chcę to zrobić dobrze)
Vilius Normantas
6
Proszę nigdy tego nie używać. Wynik wcale nie jest jednolity.
Navin
2
Największym problemem jest to, że zaokrąglenie spowoduje, że najniższy bit będzie bardzo niejednolity. Musi boundteż być mniejsza od największej liczby całkowitej, którą można zakodować w postaci podwójnej, 2 ^ 53.
Aleksandr Dubinsky,
12

Powyższe metody działają świetnie. Jeśli używasz apache commons (org.apache.commons.math.random), sprawdź RandomData. Ma metodę: nextLong (długa dolna, długa górna)

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
źródło
3
Dla potomnych: RandomData jest przestarzała w 4.0. Użyj commons.apache.org/proper/commons-math/apidocs/org/apache/…
Michael Tontchev
11

Użyj operatora „%”

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

Używając operatora „%”, bierzemy resztę po podzieleniu przez maksymalną wartość. Pozostają nam tylko liczby od 0 (włącznie) do dzielnika (wyłączne).

Na przykład:

public long randLong(long min, long max) {
    return (new java.util.Random().nextLong() % (max - min)) + min;
}
Przelew septyczny
źródło
To miłe, ale powinieneś sprawdzićif (max == min)
khcpietro
A także sprawdźif (nextLong() >= 0)
khcpietro 10.04.14
6
FYI: To nie zawsze zapewnia równomierną dystrybucję i jest naprawdę złe dla niektórych dużych zakresów. Na przykład, jeśli min = 0a max = 2 * (MAX_LONG / 3), to jesteś dwa razy częściej, aby uzyskać wartość [0, MAX_LONG / 3], jak mają się jeden w [MAX_LONG / 3, 2 * (MAX_LONG / 3)].
Nick
Ten kod nie zadziała. jeśli nextLongzwraca wartość ujemną, reszta będzie ujemna, a wartość będzie poza zakresem.
Arnaud,
3

Dalsze ulepszanie odpowiedzi KennyTM: Implementacja podklasy uwzględniająca rzeczywistą implementację w Javie 8 to:

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
źródło
Wiem, że jest to stara odpowiedź i raczej nie będzie używana, ale ta część jest ewidentnie niepoprawna: po if ((bound & m) == 0) { r = (bound * r) >> (Long.SIZE - 1); } pierwsze, za pomocą testów jednostkowych łatwo jest wykazać, że w rzeczywistości nie daje to liczb z zakresu [0, związane). Po drugie, jest to niepotrzebnie skomplikowane: r = r & mprzyniosłoby pożądany rezultat, i to właśnie robi obecna implementacja Java 8. Możliwe, że implementacja była inna, gdy została napisana ta odpowiedź, ale nie mogło być to, co pokazano.
E. Biskup
3

Jeśli chcesz mieć równomiernie rozłożoną długość pseudolosową w zakresie [0, m), spróbuj użyć operatora modulo i metody wartości bezwzględnej w połączeniu z nextLong()metodą, jak pokazano poniżej:

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

Gdzie randjest twój losowy obiekt.

Operator modulo dzieli dwie liczby i wyświetla pozostałą część tych liczb. Na przykład, 3 % 2to 1, ponieważ pozostała część 3 i 2, jest 1.

Ponieważ nextLong()generuje równomiernie rozłożoną długość pseudolosową w zakresie [- (2 ^ 48), 2 ^ 48) (lub gdzieś w tym zakresie), będziesz musiał wziąć wartość bezwzględną. Jeśli tego nie zrobisz, modulo nextLong()metody ma 50% szans na zwrócenie wartości ujemnej, która jest poza zakresem [0, m).

To, czego początkowo zażądałeś, było równomiernie rozłożonym pseudolosowym długością w zakresie [0,100). Robi to następujący kod:

Math.abs(rand.nextLong()) % 100;
TheGamePlayer 40
źródło
1
modulo jest stronniczy, nie używaj go do losowego stackoverflow.com/a/10984975/1166266
Sirens,
2

Co powiesz na to:

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

?

programista Androida
źródło
Wszystko w porządku, z wyjątkiem części, które należą do frameworka (chyba).
Damir Olejar
2

Poniższa metoda zwróci wartość od 10000000000 do 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
źródło
Kiedy resetuję długi min = 1L; long max = 10L; Wynikowa liczba losowa wykracza poza wartość maksymalną!
Raj Rajen
Powinien być random.nextLong ()% (max - min) + min;
Jay Jodiwal
2

Z API Java 8

Rzeczywistą implementację można byłoby łatwiej pobrać z dokumentu API https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long - używają go do generuje strumień długich. Twoje pochodzenie może mieć wartość „0”, jak w pytaniu.

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;
}
Vitaliy
źródło
1

Ze strony w Random :

Metoda nextLong jest implementowana przez klasę Random, tak jakby przez:

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

Ponieważ klasa Random używa ziarna zawierającego tylko 48 bitów, algorytm ten nie zwróci wszystkich możliwych długich wartości.

Więc jeśli chcesz uzyskać Long, nie uzyskasz już pełnego zakresu 64-bitowego.

Sugerowałbym, że jeśli masz zakres zbliżony do potęgi 2, zbuduj Longjak w tym fragmencie, w ten sposób:

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

aby na przykład uzyskać zakres 35 bitów.

Phil
źródło
2
Ale dokumentacja mówi: „Wszystkie 2 ^ 64 możliwe długie wartości są generowane z (w przybliżeniu) równym prawdopodobieństwem”. Najwyraźniej metoda nextLong () powinna zwrócić wszystkie możliwe wartości. Przy okazji, jak długość ziarna jest powiązana z rozkładem wartości?
Vilius Normantas
0

Metody wykorzystujące r.nextDouble()powinny wykorzystywać:

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


long number = x+(((long)r.nextDouble())*(y-x));
J Low
źródło
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
źródło
1
Nie powinieneś tworzyć Randominstancji w trybie hoc, nie powinieneś łapać Throwables ani innych wyjątków, jeśli nie są potrzebne, powinieneś rejestrować błędy za pomocą jakiegoś frameworka logowania (np. SLF4J) zamiast używać printStackTrace.
Boguś
0

Jeśli możesz korzystać ze strumieni Java, możesz spróbować następujących rozwiązań:

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

Spowoduje to wygenerowanie liczb w podanym zakresie na długie długości.

Sridhar Sg
źródło
0
import java.util*;

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

To powinno działać

Liash101
źródło
-4

// użyj czasu systemowego jako wartości początkowej, aby uzyskać dobrą liczbę losową

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

// Pętla, aż uzyskasz liczbę większą lub równą 0 i mniejszą niż n

Valentinos Ioannou
źródło
1
Może to być wyjątkowo nieefektywne. A jeśli njest 1 lub powiedzmy 2? Pętla wykona wiele iteracji.
Magnilex