Java: bilangan panjang acak dalam rentang 0 <= x <n


138

Kelas acak memiliki metode untuk menghasilkan int acak dalam rentang tertentu. Sebagai contoh:

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

Ini akan menghasilkan bilangan int lebih atau sama dengan 0 dan kurang dari 100. Saya ingin melakukan hal yang sama persis dengan bilangan panjang.

long y = magicRandomLongGenerator(100);

Kelas acak hanya memiliki nextLong (), tetapi tidak memungkinkan untuk menyetel rentang.



1
Sudahkah Anda mempertimbangkan untuk mengambil acak panjang Anda dan mengambil mod dari jangkauan Anda? (Tentu saja, jika kisarannya hanya 100, saya akan menghasilkan int random dan membuangnya menjadi panjang.)
Hot Licks

java.util.Randomhanya menggunakan distribusi 48 bit (lihat detail implementasi), jadi tidak akan memiliki distribusi normal.
Geoffrey De Smet

1
Di zaman modern, orang dapat mempertimbangkan untuk menggunakan org.apache.commons.lang3.RandomUtils # nextLong.
reallynice

Jawaban:


156

Mulai dari Java 7 (atau Android API Level 21 = 5.0+) Anda bisa langsung menggunakan ThreadLocalRandom.current().nextLong(n)(untuk 0 ≤ x <n) dan ThreadLocalRandom.current().nextLong(m, n)(untuk m ≤ x <n). Lihat jawaban @Alex untuk detailnya.


Jika Anda terjebak dengan Java 6 (atau Android 4.x), Anda perlu menggunakan perpustakaan eksternal (misalnya org.apache.commons.math3.random.RandomDataGenerator.getRandomGenerator().nextLong(0, n-1), lihat jawaban @mawaldne ), atau menerapkan milik Anda sendiri nextLong(n).

Menurut https://docs.oracle.com/javase/1.5.0/docs/api/java/util/Random.html nextInt diimplementasikan sebagai

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

Jadi kami dapat memodifikasi ini untuk melakukan 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
Saya mengalami masalah dengan bagian "2 ^ x pemeriksaan". Ada ide?
Vilius Normantas

@Vilius: Pengecekan 2 ^ x hanya membuat pembuatan lebih cepat karena langsung menggunakan rng.nextLong() % nakan memberikan nilai yang seragam (anggap semua bit baik). Anda dapat mengabaikan bagian itu jika Anda mau.
kennytm

Jika saya mau m <= x <= n, bagaimana Anda akan mengubah solusi Anda?
BJ Peter DeLaCruz

6
@BJPeterDeLaCruz: Nomor acak antara mdan ndapat diperoleh dengan nomor acak antara 0dan n-m, lalu tambahkan m.
kennytm

85

ThreadLocalRandom

ThreadLocalRandommemiliki nextLong(long bound)metode.

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

Ini juga memiliki nextLong(long origin, long bound)jika Anda membutuhkan asal selain 0. Lulus asal (inklusif) dan terikat (eksklusif).

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

SplittableRandommemiliki nextLongmetode yang sama dan memungkinkan Anda untuk memilih benih jika Anda ingin urutan angka yang dapat direproduksi.


5
Jawaban ini jauh lebih sederhana dan karena itu lebih berguna daripada jawaban yang paling banyak dipilih.
yurin

2
Bagi mereka yang mengembangkan Android, perhatikan bahwa itu hanya tersedia dari API 21 (Lollipop, Android 5.0): developer.android.com/reference/java/util/concurrent/…
pengembang android

76

Metode standar untuk menghasilkan angka (tanpa metode utilitas) dalam suatu rentang adalah dengan hanya menggunakan ganda dengan rentang:

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

akan memberi Anda jarak antara 0 (inklusif) dan rentang (eksklusif). Demikian pula jika Anda menginginkan angka antara x dan y:

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

akan memberi Anda long dari 1234567 (inklusif) sampai 123456789 (eksklusif)

Catatan: centang tanda kurung, karena mentransmisikan ke panjang memiliki prioritas lebih tinggi daripada perkalian.


5
Ide pertama saya persis seperti ini. Tapi sepertinya agak janggal. Dan saya khawatir tentang keseragaman distribusi (bukan karena saya benar-benar membutuhkannya, saya hanya ingin melakukannya dengan benar)
Vilius Normantas

6
Tolong jangan pernah menggunakan ini. Outputnya sama sekali tidak seragam.
Navin

2
Masalah terbesar adalah bahwa pembulatan akan membuat bit terendah menjadi sangat tidak seragam. Selain itu, boundharus kurang dari bilangan bulat terbesar yang dapat dienkode dalam double, 2 ^ 53.
Aleksandr Dubinsky

12

Metode di atas bekerja dengan baik. Jika Anda menggunakan apache commons (org.apache.commons.math.random) periksa RandomData. Ini memiliki metode: nextLong (panjang lebih rendah, panjang atas)

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
Untuk anak cucu: RandomData tidak digunakan lagi di 4.0. Gunakan commons.apache.org/proper/commons-math/apidocs/org/apache/…
Michael Tontchev

11

Gunakan operator '%'

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

Dengan menggunakan operator '%', kami mengambil sisanya bila dibagi dengan nilai maksimum Anda. Ini menyisakan kita hanya dengan angka dari 0 (inklusif) hingga pembagi (eksklusif).

Sebagai contoh:

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

Ini bagus, tetapi Anda harus memeriksaif (max == min)
khcpietro

Dan juga periksaif (nextLong() >= 0)
khcpietro

6
FYI: Ini tidak selalu memberikan distribusi yang seragam, dan itu sangat buruk untuk beberapa rentang yang besar. Misalnya, jika min = 0dan max = 2 * (MAX_LONG / 3), maka Anda dua kali lebih mungkin untuk mendapatkan nilai daripada [0, MAX_LONG / 3]jika Anda mendapatkannya [MAX_LONG / 3, 2 * (MAX_LONG / 3)].
Nick

Kode ini tidak akan berfungsi. jika nextLongmengembalikan nilai negatif, sisanya akan menjadi negatif, dan nilainya akan berada di luar rentang.
Arnaud

3

Lebih jauh meningkatkan jawaban kennytm: Implementasi subclass yang mempertimbangkan implementasi aktual di Java 8 akan menjadi:

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

Saya tahu ini adalah jawaban lama dan tidak mungkin digunakan, tetapi bagian ini terbukti salah: if ((bound & m) == 0) { r = (bound * r) >> (Long.SIZE - 1); } Pertama, mudah untuk menunjukkan dengan pengujian unit bahwa ini tidak benar-benar menghasilkan angka dalam kisaran [0, terikat). Kedua, ini tidak perlu rumit: r = r & makan mencapai hasil yang diinginkan, dan pada dasarnya itulah yang dilakukan implementasi Java 8 saat ini. Mungkin saja implementasinya berbeda ketika jawaban ini ditulis, tetapi tidak mungkin apa yang ditampilkan.
E. Bishop

3

Jika Anda menginginkan pseudorandom terdistribusi seragam dalam kisaran [0, m), coba gunakan operator modulo dan metode nilai absolut yang digabungkan dengan nextLong()metode seperti yang terlihat di bawah ini:

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

Di mana randobjek Random Anda.

Operator modulo membagi dua angka dan mengeluarkan sisa angka tersebut. Sebagai contoh, 3 % 2adalah 1karena sisa 3 dan 2 adalah 1.

Karena nextLong()menghasilkan pseudorandom yang terdistribusi seragam dalam rentang [- (2 ^ 48), 2 ^ 48) (atau di suatu tempat dalam rentang itu), Anda perlu mengambil nilai absolutnya. Jika tidak, modulo nextLong()metode memiliki peluang 50% untuk mengembalikan nilai negatif, yang berada di luar rentang [0, m).

Apa yang awalnya Anda minta adalah pseudorandom yang didistribusikan secara seragam di kisaran [0,100). Kode berikut melakukannya:

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

1
modulo bias, jangan gunakan untuk stackoverflow
acak.com/a/10984975/1166266

2

Bagaimana dengan ini:

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

?


Tidak apa-apa, kecuali bagian-bagian yang termasuk dalam kerangka kerja (saya kira).
Damir Olejar

2

Metode di bawah ini akan mengembalikan Anda nilai antara 10000000000 hingga 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;

}

Ketika saya mengatur ulang min lama = 1L; max panjang = 10L; Angka acak yang dihasilkan melampaui Nilai maks!
Raj Rajen

Ini harus random.nextLong ()% (max - min) + min;
Jay Jodiwal

2

Dari Java 8 API

Mungkin lebih mudah untuk mengambil implementasi aktual dari dokumen API https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long- mereka menggunakannya untuk menghasilkan aliran rindu. Dan asal Anda bisa jadi "0" seperti di pertanyaan.

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

Dari halaman di Random :

Metode nextLong diimplementasikan oleh kelas Random seolah-olah dengan:

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

Karena class Random menggunakan seed dengan hanya 48 bit, algoritma ini tidak akan mengembalikan semua kemungkinan nilai panjang.

Jadi, jika Anda ingin mendapatkan Long, Anda tidak akan mendapatkan kisaran 64 bit penuh.

Saya akan menyarankan bahwa jika Anda memiliki rentang yang mendekati pangkat 2, Anda membangun Longseperti dalam cuplikan itu, seperti ini:

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

untuk mendapatkan rentang 35 bit, misalnya.


2
Tapi dokumentasinya mengatakan "Semua 2 ^ 64 kemungkinan nilai panjang diproduksi dengan (kira-kira) probabilitas yang sama." Jadi tampaknya metode nextLong () harus mengembalikan semua nilai yang mungkin .. Btw, berapa panjang benih terkait dengan distribusi nilai?
Vilius Normantas

0

Metode yang menggunakan r.nextDouble()harus menggunakan:

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
Anda tidak boleh membuat Randomcontoh di hoc, Anda tidak boleh menangkap Throwables atau pengecualian lain jika tidak diperlukan, Anda harus mencatat kesalahan dengan beberapa jenis kerangka kerja logging (yaitu SLF4J) daripada menggunakan printStackTrace.
Boguś

0

Jika Anda dapat menggunakan aliran java, Anda dapat mencoba yang berikut ini:

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

Ini akan menghasilkan angka dalam rentang yang diberikan untuk waktu yang lama.


0
import java.util*;

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

Ini seharusnya berhasil


-4

// gunakan waktu sistem sebagai nilai benih untuk mendapatkan nomor acak yang baik

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

// Ulangi sampai mendapatkan angka yang lebih besar atau sama dengan 0 dan lebih kecil dari n


1
Ini bisa jadi sangat tidak efisien. Bagaimana jika n1, atau katakan 2? Loop akan melakukan banyak iterasi.
Magnilex
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.