Java: número longo aleatório no intervalo 0 <= x <n

135

A classe Random possui um método para gerar int aleatório em um determinado intervalo. Por exemplo:

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

Isso geraria um número int maior ou igual a 0 e menor que 100. Eu gostaria de fazer exatamente o mesmo com o número longo.

long y = magicRandomLongGenerator(100);

A classe aleatória possui apenas nextLong (), mas não permite definir o intervalo.

Vilius Normantas
fonte
Relacionado, pode ser útil: stackoverflow.com/questions/2290057/…
TJ Crowder
1
Você já pensou em obter seu longo aleatório e fazer o mod do seu alcance? (É claro que, se o intervalo é de apenas 100 eu produzir um int aleatório e lançá-lo por muito tempo.)
Hot Licks
java.util.Randomusa apenas uma distribuição de 48 bits (consulte os detalhes da implementação), portanto não terá uma distribuição normal.
Geoffrey De Smet
1
Nos dias modernos, pode-se considerar o uso de org.apache.commons.lang3.RandomUtils # nextLong.
reallynice

Respostas:

149

A partir do Java 7 (ou Android API Level 21 = 5.0+), você poderia usar diretamente ThreadLocalRandom.current().nextLong(n)(para 0 ≤ x <n) e ThreadLocalRandom.current().nextLong(m, n)(para m ≤ x <n). Consulte a resposta do @Alex para obter detalhes.


Se você está com o Java 6 (ou Android 4.x), precisa usar uma biblioteca externa (por exemplo org.apache.commons.math3.random.RandomDataGenerator.getRandomGenerator().nextLong(0, n-1), consulte a resposta de @mawaldne ) ou implementar sua própria nextLong(n).

De acordo com https://docs.oracle.com/javase/1.5.0/docs/api/java/util/Random.html, nextInt é implementado 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;
 }

Portanto, podemos modificar isso para executar 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
fonte
1
Estou tendo alguns problemas com a parte "2 ^ x check". Alguma ideia?
Vilius Normantas 30/03
@Vilius: A verificação 2 ^ x apenas torna a geração mais rápida, porque usar diretamente rng.nextLong() % nfornecerá valores uniformes (suponha que todos os bits sejam bons). Você pode ignorar essa parte, se quiser.
Kennytm 30/03/10
Se eu quiser m <= x <= n, como você modificaria sua solução?
BJ Peter DeLaCruz
6
@BJPeterDeLaCruz: Um número aleatório entre me npode ser obtido com um número aleatório entre 0e n-m, em seguida, adicione m.
Kennytm
84

ThreadLocalRandom

ThreadLocalRandomtem um nextLong(long bound)método

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

Ele também possui nextLong(long origin, long bound)se você precisar de uma origem diferente de 0. Passe a origem (inclusive) e o limite (exclusivo).

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

SplittableRandomtem os mesmos nextLongmétodos e permite que você escolha uma semente se você quiser uma sequência reprodutível de números.

Alex - GlassEditor.com
fonte
5
Esta resposta é muito mais simples e, portanto, mais útil que a mais votada.
yurin 10/11/2015
2
Para aqueles que desenvolvem para Android, observe que ele está disponível apenas na API 21 (Lollipop, Android 5.0): developer.android.com/reference/java/util/concurrent/…
desenvolvedor android
75

O método padrão para gerar um número (sem um método utilitário) em um intervalo é apenas usar o dobro com o intervalo:

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

fornecerá um tempo entre 0 (inclusive) e intervalo (exclusivo). Da mesma forma, se você deseja um número entre x e y:

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

fornecerá um longo tempo de 1234567 (inclusive) a 123456789 (exclusivo)

Nota: verifique os parênteses, porque a conversão para long tem prioridade mais alta que a multiplicação.

M. Jessup
fonte
5
Minha primeira ideia foi exatamente isso. Mas parece ser um pouco deselegante. E eu estou preocupado com uniformness da distribuição (não é que eu realmente precisa dele, eu só quero fazer isso direito)
Vilius Normantas
6
Por favor, nunca use isso. A saída não é uniforme.
Navin
2
O maior problema é que o arredondamento tornará o bit mais baixo muito uniforme. Além disso, boundterá que ser menor que o maior número inteiro que pode ser codificado em um duplo, 2 ^ 53.
Aleksandr Dubinsky 25/03
12

Os métodos acima funcionam muito bem. Se você estiver usando o apache commons (org.apache.commons.math.random), consulte RandomData. Ele tem um método: nextLong (long lower, long upper)

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
fonte
3
Para a posteridade: RandomData está obsoleto no 4.0. Use commons.apache.org/proper/commons-math/apidocs/org/apache/…
Michael Tontchev
11

Use o operador '%'

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

Usando o operador '%', pegamos o restante quando dividido pelo seu valor máximo. Isso nos deixa com apenas números de 0 (inclusive) ao divisor (exclusivo).

Por exemplo:

public long randLong(long min, long max) {
    return (new java.util.Random().nextLong() % (max - min)) + min;
}
Estouro séptico
fonte
Isso é legal, mas você deve verificarif (max == min)
khcpietro
E também verifiqueif (nextLong() >= 0)
khcpietro
6
FYI: Isso nem sempre dá uma distribuição uniforme e é muito ruim para alguns intervalos grandes. Por exemplo, se min = 0e max = 2 * (MAX_LONG / 3), então você tem duas vezes mais chances de obter um valor do [0, MAX_LONG / 3]que um [MAX_LONG / 3, 2 * (MAX_LONG / 3)].
21415 Nick
Este código não funcionará. se nextLongretornar um valor negativo, o restante será negativo e o valor estará fora do intervalo.
Arnaud
3

Melhorando ainda mais a resposta do kennytm: Uma implementação de subclasse levando em consideração a implementação real no Java 8 seria:

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
fonte
Sei que essa é uma resposta antiga e improvável de ser usada, mas esta parte é comprovadamente incorreta: if ((bound & m) == 0) { r = (bound * r) >> (Long.SIZE - 1); } Primeiro, é fácil mostrar com testes de unidade que isso realmente não produz números no intervalo [0, vinculado). Segundo, é desnecessariamente elaborado: r = r & matingiria o resultado desejado, e é basicamente isso que a implementação atual do Java 8 faz. É possível que a implementação tenha sido diferente quando essa resposta foi escrita, mas não pode ter sido o que é mostrado.
E. Bishop
3

Se você deseja um pseudoaleatório uniformemente distribuído no intervalo de [0, m), tente usar o operador modulo e o método do valor absoluto combinado com o nextLong()método conforme mostrado abaixo:

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

Onde randestá o seu objeto aleatório.

O operador módulo divide dois números e gera o restante desses números. Por exemplo, 3 % 2é 1porque o restante de 3 e 2 é 1.

Como nextLong()gera um pseudoaleatório uniformemente distribuído no intervalo de [- (2 ^ 48), 2 ^ 48) (ou em algum lugar desse intervalo), você precisará obter o valor absoluto dele. Caso contrário, o módulo do nextLong()método tem 50% de chance de retornar um valor negativo, que está fora do intervalo [0, m).

O que você solicitou inicialmente foi um pseudoaleatório uniformemente distribuído, no intervalo de [0,100). O código a seguir faz isso:

Math.abs(rand.nextLong()) % 100;
TheGamePlayer 40
fonte
1
módulo é tendencioso, não o use para stackoverflow.com/a/10984975/1166266
Sirens
2

Que tal agora:

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

?

desenvolvedor android
fonte
Está tudo bem, exceto as partes que pertencem a uma estrutura (eu acho).
Damir Olejar
2

O método abaixo retornará um valor entre 10000000000 e 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
fonte
Quando eu redefinir min min = 1L; max longo = 10L; O número aleatório resultante vai além do valor máximo!
Raj Rajen
Deve ser aleatório.nextLong ()% (max - min) + min;
Jay Jodiwal
2

Da API do Java 8

Poderia ser mais fácil obter a implementação real do documento da API https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long- eles estão usando isso para gerar fluxo de longos. E sua origem pode ser "0", como na pergunta.

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
fonte
1

Na página Random :

O método nextLong é implementado pela classe Random como se por:

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

Como a classe Random usa uma semente com apenas 48 bits, esse algoritmo não retornará todos os valores longos possíveis.

Portanto, se você deseja obter um Long, você já não terá o intervalo completo de 64 bits.

Eu sugeriria que, se você tem um alcance que cai perto de uma potência de 2, você cria o Longcomo nesse snippet, assim:

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

para obter um intervalo de 35 bits, por exemplo.

Phil
fonte
2
Mas a documentação diz "Todos os 2 ^ 64 valores longos possíveis são produzidos com (aproximadamente) probabilidade igual". Então, aparentemente, o método nextLong () deve retornar todos os valores possíveis. Btw, como o comprimento da semente está relacionado à distribuição de valores?
Vilius Normantas 30/03
0

Os métodos usando o r.nextDouble()devem usar:

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


long number = x+(((long)r.nextDouble())*(y-x));
J Baixo
fonte
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
fonte
1
Você não deve criar Randominstâncias no hoc, não deve capturar Throwables ou outras exceções, se não for necessário, deve registrar erros com algum tipo de estrutura de log (por exemplo, SLF4J) em vez de usar printStackTrace.
Boguś 23/09/17
0

Se você pode usar fluxos java, pode tentar o seguinte:

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

Isso irá gerar números no intervalo especificado por longos.

Sridhar Sg
fonte
0
import java.util*;

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

Isso deve funcionar

Liash101
fonte
-4

// use a hora do sistema como valor inicial para obter um bom número aleatório

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

// Faz loop até obter um número maior ou igual a 0 e menor que n

Valentinos Ioannou
fonte
1
Isso pode ser extremamente ineficiente. E se nfor 1 ou dizer 2? O loop executará muitas iterações.
Magnilex