Apakah mungkin untuk menaburkan generator angka acak (Math.random) dalam Javascript?
Apakah mungkin untuk menaburkan generator angka acak (Math.random) dalam Javascript?
Jawaban:
Tidak, tidak, tapi cukup mudah untuk menulis generator Anda sendiri, atau lebih baik menggunakan yang sudah ada. Lihat: pertanyaan terkait ini .
Juga, lihat blog David Bau untuk informasi lebih lanjut tentang penyemaian .
CATATAN: Meskipun (atau lebih tepatnya, karena) ringkas dan elegan, algoritma ini sama sekali tidak berkualitas tinggi dalam hal keacakan. Cari misalnya yang tercantum dalam jawaban ini untuk hasil yang lebih baik.
(Awalnya diadaptasi dari ide cerdas yang disajikan dalam komentar untuk jawaban lain.)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
Anda dapat mengatur seed
menjadi nomor apa saja, hindari saja nol (atau kelipatan Math.PI).
Keanggunan solusi ini, menurut saya, berasal dari tidak adanya angka "ajaib" (selain 10.000, yang mewakili tentang jumlah minimum digit yang harus Anda buang untuk menghindari pola aneh - lihat hasil dengan nilai 10 , 100 , 1000 ). Keringkasan juga bagus.
Ini sedikit lebih lambat daripada Math.random () (dengan faktor 2 atau 3), tapi saya percaya ini tentang secepat solusi lain yang ditulis dalam JavaScript.
Saya telah mengimplementasikan sejumlah fungsi Pseudorandom number generator (PRNG) yang baik, pendek dan cepat dalam JavaScript biasa. Semuanya dapat diunggulkan dan memberikan angka kualitas yang baik.
Pertama-tama, berhati-hatilah untuk menginisialisasi PRNG Anda dengan benar. Sebagian besar generator di bawah ini tidak memiliki prosedur penghasil benih built-in (demi kesederhanaan), tetapi menerima satu atau lebih nilai 32-bit sebagai keadaan awal PRNG. Benih yang serupa (misalnya benih sederhana 1 dan 2) dapat menyebabkan korelasi pada PRNG yang lebih lemah, menghasilkan output yang memiliki sifat yang sama (seperti tingkat yang dihasilkan secara acak serupa). Untuk menghindari hal ini, praktik terbaik adalah menginisialisasi PRNG dengan benih yang didistribusikan dengan baik.
Untungnya, fungsi hash sangat baik dalam menghasilkan benih untuk PRNG dari string pendek. Fungsi hash yang baik akan menghasilkan hasil yang sangat berbeda bahkan ketika dua string serupa. Berikut ini contoh berdasarkan fungsi pencampuran MurmurHash3:
function xmur3(str) {
for(var i = 0, h = 1779033703 ^ str.length; i < str.length; i++)
h = Math.imul(h ^ str.charCodeAt(i), 3432918353),
h = h << 13 | h >>> 19;
return function() {
h = Math.imul(h ^ h >>> 16, 2246822507);
h = Math.imul(h ^ h >>> 13, 3266489909);
return (h ^= h >>> 16) >>> 0;
}
}
Setiap panggilan berikutnya ke fungsi kembali dari xmur3
menghasilkan "acak" nilai hash 32-bit baru yang akan digunakan sebagai benih di PRNG a. Begini cara Anda menggunakannya:
// Create xmur3 state:
var seed = xmur3("apples");
// Output four 32-bit hashes to provide the seed for sfc32.
var rand = sfc32(seed(), seed(), seed(), seed());
// Output one 32-bit hash to provide the seed for mulberry32.
var rand = mulberry32(seed());
// Obtain sequential random numbers like so:
rand();
rand();
Atau, cukup pilih beberapa data dummy untuk mengisi benih, dan majukan generator beberapa kali (12-20 iterasi) untuk mencampur kondisi awal secara menyeluruh. Ini sering terlihat dalam implementasi referensi PRNG, tetapi ia membatasi jumlah keadaan awal.
var seed = 1337 ^ 0xDEADBEEF; // 32-bit seed with optional XOR value
// Pad seed with Phi, Pi and E.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
var rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
for (var i = 0; i < 15; i++) rand();
Output dari fungsi-fungsi PRNG ini menghasilkan angka 32-bit positif (0 hingga 2 32 -1) yang kemudian dikonversi ke angka floating-point antara 0-1 (0 inklusif, 1 eksklusif) setara dengan Math.random()
, jika Anda menginginkan angka acak untuk rentang tertentu, baca artikel ini di MDN . Jika Anda hanya menginginkan bit mentah, cukup hapus operasi pembagian akhir.
Hal lain yang perlu diperhatikan adalah keterbatasan JS. Angka hanya dapat mewakili seluruh bilangan bulat hingga resolusi 53-bit. Dan ketika menggunakan operasi bitwise, ini dikurangi menjadi 32. Ini membuatnya sulit untuk mengimplementasikan algoritma yang ditulis dalam C atau C ++, yang menggunakan angka 64-bit. Porting kode 64-bit membutuhkan shims yang dapat secara drastis mengurangi kinerja. Jadi demi kesederhanaan dan efisiensi, saya hanya mempertimbangkan algoritma yang menggunakan matematika 32-bit, karena secara langsung kompatibel dengan JS.
Sekarang, lanjutkan ke generator. (Saya memelihara daftar lengkap dengan referensi di sini )
sfc32 adalah bagian dari suite pengujian nomor acak PractRand (yang lolos tentu saja). sfc32 memiliki status 128-bit dan sangat cepat di JS.
function sfc32(a, b, c, d) {
return function() {
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
var t = (a + b) | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
d = d + 1 | 0;
t = t + d | 0;
c = c + t | 0;
return (t >>> 0) / 4294967296;
}
}
Mulberry32 adalah generator sederhana dengan keadaan 32-bit, tetapi sangat cepat dan memiliki kualitas yang baik (penulis menyatakan bahwa ia lulus semua pengujian suite pengujian gjrand dan memiliki periode 2 32 penuh , tetapi saya belum memverifikasi).
function mulberry32(a) {
return function() {
var t = a += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
}
Saya akan merekomendasikan ini jika Anda hanya membutuhkan PRNG yang sederhana namun layak dan tidak perlu miliaran angka acak (lihat masalah Ulang Tahun ).
Pada Mei 2018, xoshiro128 ** adalah anggota baru keluarga Xorshift , oleh Vigna / Blackman (yang juga menulis xoroshiro, yang digunakan di Chrome). Ini adalah generator tercepat yang menawarkan status 128-bit.
function xoshiro128ss(a, b, c, d) {
return function() {
var t = b << 9, r = a * 5; r = (r << 7 | r >>> 25) * 9;
c ^= a; d ^= b;
b ^= c; a ^= d; c ^= t;
d = d << 11 | d >>> 21;
return (r >>> 0) / 4294967296;
}
}
Para penulis mengklaim itu lulus tes keacakan dengan baik ( meskipun dengan peringatan ). Peneliti lain telah menunjukkan bahwa gagal beberapa tes di TestU01 (khususnya LinearComp dan BinaryRank). Dalam praktiknya, seharusnya tidak menyebabkan masalah ketika float digunakan (seperti implementasi ini), tetapi dapat menyebabkan masalah jika mengandalkan bit mentah yang rendah.
Ini adalah JSF atau 'smallprng' oleh Bob Jenkins (2007), pria yang membuat ISAAC dan SpookyHash . Itu melewati tes PractRand dan harus cukup cepat, meskipun tidak secepat SFC.
function jsf32(a, b, c, d) {
return function() {
a |= 0; b |= 0; c |= 0; d |= 0;
var t = a - (b << 27 | b >>> 5) | 0;
a = b ^ (c << 17 | c >>> 15);
b = c + d | 0;
c = d + t | 0;
d = a + t | 0;
return (d >>> 0) / 4294967296;
}
}
LCG sangat cepat dan sederhana, tetapi kualitas keacakannya sangat rendah, sehingga penggunaan yang tidak benar dapat menyebabkan bug di program Anda! Meskipun demikian, ini jauh lebih baik daripada beberapa jawaban yang menyarankan untuk menggunakan Math.sin
atau Math.PI
! Ini adalah one-liner, yang bagus :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
Implementasi ini disebut RNG standar minimal seperti yang diusulkan oleh Park – Miller pada tahun 1988 & 1993 dan diimplementasikan dalam C ++ 11 sebagai minstd_rand
. Perlu diingat bahwa statusnya adalah 31-bit (31 bit memberi 2 miliar kemungkinan status, 32 bit memberikan dua kali lipat dari itu). Ini adalah tipe PRNG yang coba diganti oleh orang lain!
Ini akan berhasil, tapi saya tidak akan menggunakannya kecuali Anda benar - benar membutuhkan kecepatan dan tidak peduli dengan kualitas keacakan (apa sih yang acak?). Sangat cocok untuk permainan yang macet atau demo atau sesuatu. LCG menderita korelasi benih, jadi yang terbaik adalah membuang hasil pertama LCG. Dan jika Anda bersikeras menggunakan LCG, menambahkan nilai kenaikan dapat meningkatkan hasil, tetapi mungkin latihan sia-sia ketika ada pilihan yang jauh lebih baik.
Tampaknya ada pengganda lain yang menawarkan keadaan 32-bit (peningkatan ruang-ruang):
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
Nilai LCG ini berasal dari: P. L'Ecuyer: Tabel Linear Congruential Generator dengan berbagai ukuran dan struktur kisi yang bagus, 30 April 1997.
seed = (seed * 185852 + 1) % 34359738337
.
Math.imul
memungkinkan untuk meluap seperti saat menggunakan perkalian dalam C pada bilangan bulat 32-bit. Apa yang Anda sarankan adalah LCG yang memanfaatkan ruang integer JS lengkap, yang tentunya juga merupakan area yang menarik untuk dijelajahi. :)
Tidak, tapi ini generator pseudorandom sederhana, sebuah implementasi Multiply-with-carry I yang diadaptasi dari Wikipedia (telah dihapus sejak):
var m_w = 123456789;
var m_z = 987654321;
var mask = 0xffffffff;
// Takes any integer
function seed(i) {
m_w = (123456789 + i) & mask;
m_z = (987654321 - i) & mask;
}
// Returns number between 0 (inclusive) and 1.0 (exclusive),
// just like Math.random().
function random()
{
m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
}
EDIT: memperbaiki fungsi seed dengan membuatnya reset m_z
EDIT2: Kelemahan implementasi serius telah diperbaiki
seed
Fungsi tidak me-reset acak generator, karena mz_z
variabel berubah ketika random()
disebut. Oleh karena itu atur mz_z = 987654321
(atau nilai lain apa pun) diseed
m_w
, bukan m_z
. 2) Keduanya m_w
dan m_z
diubah BERDASARKAN pada nilai sebelumnya, sehingga ia memodifikasi hasilnya.
Algoritma Antti Sykäri bagus dan pendek. Saya awalnya membuat variasi yang menggantikan Math.random Javascript ketika Anda memanggil Math.seed, tetapi kemudian Jason berkomentar bahwa mengembalikan fungsi akan lebih baik:
Math.seed = function(s) {
return function() {
s = Math.sin(s) * 10000; return s - Math.floor(s);
};
};
// usage:
var random1 = Math.seed(42);
var random2 = Math.seed(random1());
Math.random = Math.seed(random2());
Ini memberi Anda fungsionalitas lain yang tidak dimiliki Javascript: beberapa generator acak independen. Itu sangat penting jika Anda ingin memiliki beberapa simulasi berulang yang berjalan pada saat yang sama.
Math.random
yang memungkinkan Anda memiliki beberapa generator independen, bukan?
Math.seed(42);
itu me-reset fungsi, jadi jika Anda var random = Math.seed(42); random(); random();
Anda mendapatkan 0.70...
, kemudian 0.38...
. Jika Anda mengatur ulang dengan menelepon var random = Math.seed(42);
lagi, maka saat Anda menelepon lagi random()
akan Anda dapatkan 0.70...
lagi, dan kali berikutnya Anda akan mendapatkannya 0.38...
lagi.
random
alih-alih menimpa fungsi javascript asli. Timpa Math.random
dapat menyebabkan kompiler JIST untuk mengoptimalkan semua kode Anda.
Silakan lihat karya Pierre L'Ecuyer kembali ke akhir 1980-an dan awal 1990-an. Ada juga yang lain. Membuat generator bilangan acak (semu) sendiri, jika Anda bukan ahli, cukup berbahaya, karena ada kemungkinan besar hasilnya tidak acak secara statistik atau memiliki periode kecil. Pierre (dan lainnya) telah mengumpulkan beberapa generator angka acak yang baik (pseudo) yang mudah diimplementasikan. Saya menggunakan salah satu generator LFSR-nya.
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
Phil Troy
Menggabungkan beberapa jawaban sebelumnya, ini adalah fungsi acak unggulan yang Anda cari:
Math.seed = function(s) {
var mask = 0xffffffff;
var m_w = (123456789 + s) & mask;
var m_z = (987654321 - s) & mask;
return function() {
m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask;
m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask;
var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
}
}
var myRandomFunction = Math.seed(1234);
var randomNumber = myRandomFunction();
Math.seed(0)()
mengembalikan 0.2322845458984375
, dan Math.seed(1)()
mengembalikan 0.23228873685002327
. Mengubah keduanya m_w
dan m_z
menurut benih tampaknya membantu. var m_w = 987654321 + s; var m_z = 123456789 - s;
menghasilkan distribusi nilai pertama yang bagus dengan benih yang berbeda.
Untuk menulis generator acak semu sendiri cukup sederhana.
Saran dari Dave Scotese berguna tetapi, seperti yang ditunjukkan oleh orang lain, tidak terdistribusi secara merata.
Namun, itu bukan karena argumen bilangan bulat tentang dosa. Itu hanya karena rentang dosa, yang kebetulan merupakan proyeksi satu dimensi dari sebuah lingkaran. Jika Anda mengambil sudut lingkaran, itu akan menjadi seragam.
Jadi alih-alih dosa (x) gunakan arg (exp (i * x)) / (2 * PI).
Jika Anda tidak menyukai urutan linier, campur sedikit dengan xor. Faktor sebenarnya tidak terlalu penting.
Untuk menghasilkan n angka acak semu, seseorang dapat menggunakan kode:
function psora(k, n) {
var r = Math.PI * (k ^ n)
return r - Math.floor(r)
}
n = 42; for(k = 0; k < n; k++) console.log(psora(k, n))
Harap perhatikan juga bahwa Anda tidak dapat menggunakan urutan acak semu ketika entropi nyata diperlukan.
Banyak orang yang membutuhkan generator nomor acak yang dapat di seedable dalam Javascript hari ini menggunakan modul seedrandom David Bau .
Math.random
tidak, tetapi perpustakaan ran memecahkan ini. Ini memiliki hampir semua distribusi yang dapat Anda bayangkan dan mendukung generasi nomor acak unggulan. Contoh:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
Saya telah menulis fungsi yang mengembalikan nomor acak yang diunggulkan, menggunakan Math.sin untuk memiliki nomor acak yang panjang dan menggunakan benih untuk memilih angka dari itu.
Gunakan:
seedRandom("k9]:2@", 15)
itu akan mengembalikan nomor yang Anda seeded parameter pertama adalah nilai string apa pun; benihmu. parameter kedua adalah berapa banyak digit yang akan kembali.
function seedRandom(inputSeed, lengthOfNumber){
var output = "";
var seed = inputSeed.toString();
var newSeed = 0;
var characterArray = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','y','x','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','U','R','S','T','U','V','W','X','Y','Z','!','@','#','$','%','^','&','*','(',')',' ','[','{',']','}','|',';',':',"'",',','<','.','>','/','?','`','~','-','_','=','+'];
var longNum = "";
var counter = 0;
var accumulator = 0;
for(var i = 0; i < seed.length; i++){
var a = seed.length - (i+1);
for(var x = 0; x < characterArray.length; x++){
var tempX = x.toString();
var lastDigit = tempX.charAt(tempX.length-1);
var xOutput = parseInt(lastDigit);
addToSeed(characterArray[x], xOutput, a, i);
}
}
function addToSeed(character, value, a, i){
if(seed.charAt(i) === character){newSeed = newSeed + value * Math.pow(10, a)}
}
newSeed = newSeed.toString();
var copy = newSeed;
for(var i=0; i<lengthOfNumber*9; i++){
newSeed = newSeed + copy;
var x = Math.sin(20982+(i)) * 10000;
var y = Math.floor((x - Math.floor(x))*10);
longNum = longNum + y.toString()
}
for(var i=0; i<lengthOfNumber; i++){
output = output + longNum.charAt(accumulator);
counter++;
accumulator = accumulator + parseInt(newSeed.charAt(counter));
}
return(output)
}
Pendekatan sederhana untuk seed tetap:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
Untuk angka antara 0 dan 100.
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
sedemikian rupa sehingga setiap kali Math.random
disemai dengan benih yang sama, itu akan menghasilkan serangkaian nomor acak yang sama berturut-turut. Pertanyaan ini bukan, katakanlah, tentang penggunaan / demonstrasi aktual dari Math.random
.