Ada apa dengan 181783497276652981 dan 8682522807148012 secara Acak (Java 7)?


112

Mengapa 181783497276652981dan 8682522807148012dipilih Random.java?

Berikut kode sumber yang relevan dari Java SE JDK 1.7:

/**
 * Creates a new random number generator. This constructor sets
 * the seed of the random number generator to a value very likely
 * to be distinct from any other invocation of this constructor.
 */
public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}

private static long seedUniquifier() {
    // L'Ecuyer, "Tables of Linear Congruential Generators of
    // Different Sizes and Good Lattice Structure", 1999
    for (;;) {
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L;
        if (seedUniquifier.compareAndSet(current, next))
            return next;
    }
}

private static final AtomicLong seedUniquifier
    = new AtomicLong(8682522807148012L);

Jadi, memanggil new Random()tanpa parameter seed apapun membutuhkan "seed uniquifier" saat ini dan melakukan XOR dengannya System.nanoTime(). Kemudian menggunakan 181783497276652981untuk membuat pengunikan benih lain untuk disimpan untuk panggilan berikutnya new Random().

Literal 181783497276652981Ldan 8682522807148012Ltidak ditempatkan dalam konstanta, tetapi tidak muncul di tempat lain.

Awalnya komentar itu memberi saya petunjuk mudah. Pencarian online untuk artikel itu menghasilkan artikel yang sebenarnya . 8682522807148012tidak muncul di koran, tapi 181783497276652981tidak muncul - sebagai substring dari nomor lain, 1181783497276652981yang 181783497276652981dengan 1ditambahkan.

Makalah ini mengklaim bahwa itu 1181783497276652981adalah angka yang menghasilkan "jasa" yang baik untuk generator kongruensial linier. Apakah nomor ini salah disalin ke dalam Java? Apakah 181783497276652981memiliki pahala yang dapat diterima?

Dan mengapa 8682522807148012dipilih?

Pencarian online untuk salah satu nomor tidak menghasilkan penjelasan, hanya halaman ini yang juga mengetahui penurunan 1di depan 181783497276652981.

Mungkinkah nomor lain telah dipilih yang berfungsi sebaik kedua nomor ini? Mengapa atau mengapa tidak?


Saya hanya ingin menunjukkan bahwa tidak ada konstanta yang disebutkan (bahkan yang lebih besar dengan yang ada di awal) terlalu besar untuk disesuaikan meskipun perkalian pasti akan menghasilkan luapan.
nanofarad

6
8682522807148012merupakan warisan dari versi kelas sebelumnya, seperti yang dapat dilihat pada revisi yang dibuat pada tahun 2010 . The 181783497276652981Ltampaknya menjadi salah ketik memang dan Anda bisa mengajukan laporan bug.
assylias

6
Entah itu salah ketik, yaitu bug, atau fitur dengan motivasi yang tidak diungkapkan. Anda harus bertanya pada penulisnya. Apa pun yang Anda dapatkan di sini akan menjadi opini yang kurang lebih kurang informasi. Jika menurut Anda itu bug, kirimkan laporan bug.
Marquis dari Lorne

1
Terutama mengingat jawaban yang berbeda, ini bisa menjadi dua pertanyaan terpisah untuk setiap konstanta.
Mark Hurd

1
Sedih melihat hambatan skalabilitas global yang dibangun di dalam kelas yang begitu mendasar. seedUniquifierdapat sangat dipertandingkan pada kotak 64 inti. Thread-local akan lebih terukur.
usr

Jawaban:


57
  1. Apakah nomor ini salah disalin ke dalam Java?

    Ya, sepertinya salah ketik.

  2. Apakah 181783497276652981 memiliki nilai yang dapat diterima?

    Ini dapat ditentukan dengan menggunakan algoritma evaluasi yang disajikan dalam makalah. Tetapi manfaat dari angka "asli" mungkin lebih tinggi.

  3. Dan mengapa 8682522807148012 dipilih?

    Sepertinya acak. Ini bisa jadi hasil dari System.nanoTime () saat kode ditulis.

  4. Mungkinkah nomor lain telah dipilih yang berfungsi sebaik kedua nomor ini?

    Tidak setiap angka akan sama-sama "baik". Jadi tidak.

Strategi Pembibitan

Ada perbedaan dalam skema penyemaian default antara versi yang berbeda dan implementasi JRE.

public Random() { this(System.currentTimeMillis()); }
public Random() { this(++seedUniquifier + System.nanoTime()); }
public Random() { this(seedUniquifier() ^ System.nanoTime()); }

Yang pertama tidak dapat diterima jika Anda membuat beberapa RNG secara berurutan. Jika waktu pembuatannya berada dalam kisaran milidetik yang sama, mereka akan memberikan urutan yang sepenuhnya identik. (benih yang sama => urutan yang sama)

Yang kedua tidak aman untuk benang. Beberapa utas bisa mendapatkan RNG identik saat menginisialisasi pada saat yang bersamaan. Selain itu, benih inisialisasi berikutnya cenderung berkorelasi. Bergantung pada resolusi pengatur waktu aktual dari sistem, urutan benih dapat meningkat secara linier (n, n + 1, n + 2, ...). Seperti yang dinyatakan dalam Seberapa berbedanya benih acak? dan kertas referensi Cacat umum dalam inisialisasi pembuat nomor pseudorandom , benih berkorelasi dapat menghasilkan korelasi di antara urutan sebenarnya dari beberapa RNG.

Pendekatan ketiga menciptakan benih yang didistribusikan secara acak dan dengan demikian tidak berkorelasi, bahkan di seluruh utas dan inisialisasi berikutnya. Jadi dokumen java saat ini:

Konstruktor ini menetapkan benih generator bilangan acak ke nilai yang sangat mungkin berbeda dari pemanggilan konstruktor lainnya.

dapat diperpanjang dengan "melintasi utas" dan "tidak berkorelasi"

Kualitas Urutan Benih

Tetapi urutan penyemaian yang acak hanya sebaik RNG yang mendasarinya. RNG yang digunakan untuk urutan seed pada implementasi java ini menggunakan multiplicative linear congruential generator (MLCG) dengan c = 0 dan m = 2 ^ 64. (Modulus 2 ^ 64 secara implisit diberikan oleh luapan bilangan bulat panjang 64bit) Karena nol c dan modulus pangkat-2, "kualitas" (panjang siklus, korelasi bit, ...) terbatas . Seperti yang dikatakan makalah, selain panjang siklus keseluruhan, setiap bit memiliki panjang siklus sendiri, yang menurun secara eksponensial untuk bit yang kurang signifikan. Jadi, bit yang lebih rendah memiliki pola pengulangan yang lebih kecil. (Hasil seedUniquifier () harus dibalik bit, sebelum dipotong menjadi 48-bit di RNG aktual)

Tapi itu cepat! Dan untuk menghindari perbandingan-dan-set-loop yang tidak perlu, badan loop harus cepat. Ini mungkin menjelaskan penggunaan MLCG khusus ini, tanpa penambahan, tanpa xoring, hanya satu perkalian.

Dan makalah tersebut menyajikan daftar "pengali" yang baik untuk c = 0 dan m = 2 ^ 64, sebagai 1181783497276652981.

Semua dalam semua: A untuk usaha @ JRE-developer;) Tapi ada kesalahan ketik. (Tapi siapa tahu, kecuali seseorang mengevaluasinya, ada kemungkinan bahwa petunjuk 1 yang hilang benar-benar meningkatkan RNG penyemaian.)

Tetapi beberapa pengganda pasti lebih buruk: "1" mengarah ke urutan yang konstan. "2" mengarah ke urutan pemindahan bit tunggal (entah bagaimana berkorelasi) ...

Korelasi antar-urutan untuk RNG sebenarnya relevan untuk Simulasi (Monte Carlo), di mana beberapa urutan acak dipakai dan bahkan diparalelkan. Oleh karena itu, strategi penyemaian yang baik diperlukan untuk menjalankan simulasi "independen". Oleh karena itu, standar C ++ 11 memperkenalkan konsep Urutan Benih untuk menghasilkan benih yang tidak berkorelasi.


3
Setidaknya masih ganjil, jika mereka telah menjatuhkan yang paling tidak signifikan daripada yang paling signifikan maka setiap perkalian kehilangan sedikit hingga akhirnya (setelah 62 langkah) seedUniquifiermenjadi macet di nol.
Harold

9

Jika Anda menganggap bahwa persamaan yang digunakan untuk generator bilangan acak adalah:

LCGEquation

Di mana X (n + 1) adalah bilangan berikutnya, a adalah pengali, X (n) adalah bilangan saat ini, c adalah kenaikan dan m adalah modulus.

Jika Anda melihat lebih jauh Random, a, c dan m didefinisikan di header kelas

private static final long multiplier = 0x5DEECE66DL;   //= 25214903917 -- 'a'
private static final long addend = 0xBL;               //= 11          -- 'c'
private static final long mask = (1L << 48) - 1;       //= 2 ^ 48 - 1  -- 'm'

dan melihat metode protected int next(int bits)ini adalah persamaan diimplementasikan

nextseed = (oldseed * multiplier + addend) & mask;
//X(n+1) =  (X(n)   *      a     +    c  ) mod m

Ini menyiratkan bahwa metode seedUniquifier()tersebut sebenarnya mendapatkan X (n) atau dalam kasus pertama pada inisialisasi X (0) yang sebenarnya 8682522807148012 * 181783497276652981, nilai ini kemudian dimodifikasi lebih lanjut dengan nilai System.nanoTime(). Algoritma ini konsisten dengan persamaan di atas tetapi dengan X berikut (0) = 8682522807148012, a = 181783497276652981, m = 2 ^ 64 dan c = 0. Tetapi karena mod m dari dibentuk sebelumnya oleh long overflow persamaan di atas hanya menjadi

persamaan2

Melihat kertas tersebut , nilai a = 1181783497276652981untuk m = 2 ^ 64, c = 0. Jadi tampaknya hanya salah ketik dan nilai 8682522807148012untuk X (0) yang tampaknya merupakan nomor yang dipilih secara acak dari kode lama untuk Random. Seperti yang terlihat di sini. Namun kelebihan dari nomor yang dipilih ini masih bisa valid tetapi seperti yang disebutkan oleh Thomas B. mungkin tidak “sebaik” yang ada di koran.

EDIT - Di bawah pemikiran asli sejak itu telah diklarifikasi sehingga dapat diabaikan tetapi meninggalkannya untuk referensi

Ini membawa saya pada kesimpulan:

  1. Referensi kertas bukan untuk nilai itu sendiri tetapi untuk metode yang digunakan untuk mendapatkan nilai karena perbedaan nilai a, c dan m

  2. Hanya kebetulan bahwa nilainya sama selain dari 1 di awal dan komentar salah tempat (meskipun masih berjuang untuk mempercayainya)

ATAU

Ada kesalahpahaman yang serius tentang tabel di makalah dan para pengembang baru saja memilih nilai secara acak karena pada saat dikalikan apa gunanya menggunakan nilai tabel di tempat pertama terutama karena Anda bisa memberikan memiliki nilai benih dengan cara apapun dalam hal ini nilai-nilai ini bahkan tidak diperhitungkan

Jadi, untuk menjawab pertanyaanmu

Mungkinkah nomor lain telah dipilih yang berfungsi sebaik kedua nomor ini? Mengapa atau mengapa tidak?

Ya, nomor apa pun dapat digunakan, bahkan jika Anda menentukan nilai benih saat Anda Membuat Instansiasi Acak, Anda menggunakan nilai lain. Nilai ini tidak berpengaruh pada kinerja generator, hal ini ditentukan oleh nilai a, c dan m yang di-hardcode di dalam kelas.


1
Tidak juga - Ada dua algoritme: (i) 1 untuk membuat seed acak baru setiap kali konstruktor dipanggil. Algo itu menggunakan X_n + 1 = X_n * a sederhana. Karena luapan yang lama ini setara dengan X_n + 1 = X_n * a mod m. Dengan a = 181783497276652981 dan m = 2 ^ 64. (ii) Algo lain, yang dimulai dari benih yang diberikan, menghasilkan rangkaian bilangan acak. Algo kedua itu adalah yang Anda sebutkan dan dokumen menjelaskan bahwa " Ini adalah generator nomor pseudorandom kongruensial linier, seperti yang dijelaskan oleh Knuth dalam Seni Pemrograman Komputer ".
assylias

1
@assylias Saya mengerti maksud Anda, begitu terjebak dalam kode sumber Randomdan makalah yang dikutip saya benar-benar melampaui pertanyaan asli, akan segera mengedit, terima kasih.
Java Devil

3

Sesuai tautan yang Anda berikan, mereka telah memilih ( setelah menambahkan 1 :) yang hilang ) hasil terbaik dari 2 ^ 64 karena lama tidak dapat memiliki angka dari 2 ^ 128

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.