Javascript'te rastgele sayı üretecini (Math.random) tohumlamak mümkün mü?
Javascript'te rastgele sayı üretecini (Math.random) tohumlamak mümkün mü?
Yanıtlar:
Hayır, değil, ama kendi jeneratörünüzü yazmak ya da daha iyisi var olanı kullanmak oldukça kolaydır. Göz atın: bu ilgili soru .
Ayrıca, tohumlama hakkında daha fazla bilgi için David Bau'nun bloguna bakın .
NOT: Özlüğe ve belirgin zarafete rağmen (ya da daha doğrusu), bu algoritma hiçbir şekilde rastgele olma açısından yüksek kaliteli bir algoritma değildir. Daha iyi sonuçlar için örneğin bu cevapta listelenenleri arayın .
(Başlangıçta bir yorumda başka bir cevaba sunulan zekice bir fikirden uyarlanmıştır.)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
seed
Herhangi bir sayı olarak ayarlayabilirsiniz , sadece sıfırdan (veya Math.PI'nin herhangi bir katından) kaçının.
Bu çözümün şıklığı, bence, herhangi bir "sihirli" sayının eksikliğinden geliyor (10000 dışında, tuhaf desenlerden kaçınmak için atmanız gereken minimum basamak miktarını temsil ediyor - 10 , 100 , 1000 değerlerine sahip sonuçlara bakın. ). Kısalık da güzel.
Math.random () (2 veya 3 faktörü ile) biraz daha yavaş, ancak JavaScript ile yazılmış herhangi bir çözüm kadar hızlı olduğuna inanıyorum.
Düz JavaScript'te iyi, kısa ve hızlı bir dizi Pseudorandom sayı üreteci (PRNG) işlevi uyguladım . Hepsi tohumlanabilir ve kaliteli numaralar sağlayabilir.
Her şeyden önce, PRNG'lerinizi doğru şekilde başlatmaya özen gösterin. Aşağıdaki jeneratörlerin çoğunda (basitlik amacıyla) yerleşik tohum üretme prosedürü yoktur, ancak bir veya daha fazla 32 bit değeri PRNG'nin başlangıç durumu olarak kabul edilir . Benzer tohumlar (örneğin, 1 ve 2'nin basit bir tohumu), zayıf PRNG'lerde korelasyonlara neden olabilir, bu da çıktının benzer özelliklere sahip olmasıyla sonuçlanır (rastgele üretilen seviyeler benzerdir). Bundan kaçınmak için, PRNG'leri iyi dağıtılmış bir tohumla başlatmak en iyi uygulamadır.
Neyse ki, hash fonksiyonları kısa dizelerden PRNG'ler için tohum üretmede çok iyidir. İyi bir sağlama işlevi, iki dize benzer olsa bile çok farklı sonuçlar üretir. İşte MurmurHash3'ün karıştırma fonksiyonuna dayanan bir örnek:
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;
}
}
Her bir sonraki çağrı dönüş fonksiyonu arasında xmur3
bir PRNG bir tohum olarak kullanılmak üzere yeni bir "rastgele", 32-bit karma değer üretir. Bunu nasıl kullanabileceğiniz aşağıda açıklanmıştır:
// 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();
Alternatif olarak, sadece tohumu doldurmak için kukla veriler seçin ve ilk durumu iyice karıştırmak için jeneratörü birkaç kez ilerletin (12-20 iterasyon). Bu genellikle PRNG'lerin referans uygulamalarında görülür, ancak başlangıç durumlarının sayısını sınırlandırır.
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();
Bu PRNG işlevlerinin çıktısı, rasgele sayılar istiyorsanız, 0-1 (0 dahil, 1 özel) arasında eşdeğer bir kayan nokta sayısına dönüştürülen pozitif bir 32 bit sayı (0 ila 2 32 -1) üretir MDN ile ilgili bu makaleyiMath.random()
okuyun . Yalnızca ham bitleri istiyorsanız, son bölme işlemini kaldırmanız yeterlidir.
Dikkat edilmesi gereken başka bir şey de JS'nin sınırlamalarıdır. Sayılar yalnızca 53 bit çözünürlüğe kadar tam sayıları temsil edebilir. Bitsel işlemler kullanıldığında, bu 32'ye düşürülür. Bu, 64 bit sayıları kullanan C veya C ++ ile yazılmış algoritmaların uygulanmasını zorlaştırır. 64 bit kodun taşınması, performansı önemli ölçüde azaltabilen şimler gerektirir . Basitlik ve verimlilik uğruna, sadece JS ile doğrudan uyumlu olduğu için sadece 32 bit matematik kullanan algoritmaları düşündüm.
Şimdi jeneratörlere doğru. (Tam referansları burada saklıyorum )
sfc32 , PractRand rasgele sayı test paketinin (elbette geçtiği) bir parçasıdır . sfc32 128-bit bir duruma sahiptir ve JS'de çok hızlıdır.
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 32 bit devlet ile basit bir jeneratör, ama son derece hızlı ve (yazar bunun tüm testleri geçen devletler iyi kaliteye sahip gjrand test paketi ve tam 2 sahiptir 32 dönemini ama doğrulamadım).
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;
}
}
Sadece basit ama iyi bir PRNG gerekiyorsa ve milyarlarca rasgele sayıya ihtiyacınız yoksa bunu tavsiye ederim (bkz. Doğum Günü sorunu ).
Mayıs 2018 itibariyle, xoshiro128 ** , Xighift ailesinin yeni üyesi, Vigna / Blackman (ayrıca Chrome'da kullanılan xoroshiro'u da yazdı). 128 bitlik bir durum sunan en hızlı jeneratördür.
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;
}
}
Yazarlar rastgele testlerden iyi geçtiğini iddia ediyorlar ( uyarılar olsa da ). Diğer araştırmacılar, TestU01'de (özellikle LinearComp ve BinaryRank) bazı testlerin başarısız olduğunu belirtti. Pratikte, şamandıralar kullanıldığında sorunlara neden olmamalıdır (bu uygulamalar gibi), ancak ham düşük bitlere dayanarak sorunlara neden olabilir.
Bu JSF veya ISAAC ve SpookyHash yapan Bob Jenkins (2007) tarafından 'smallprng' . O geçer olmasa kadar hızlı SFC olarak, PractRand testleri ve oldukça hızlı olmalıdır.
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 son derece hızlı ve basittir, ancak rasgeleliğinin kalitesi o kadar düşüktür ki, yanlış kullanım aslında programınızda hatalara neden olabilir! Yine de, Math.sin
veya öneren bazı cevaplardan önemli ölçüde daha iyidir veya Math.PI
! Güzel olsa da tek astarlı :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
Bu uygulamaya 1988 ve 1993 yıllarında Park-Miller tarafından önerilen ve C ++ 11 olarak uygulanan minimal standart RNG denir . Devletin 31 bit olduğunu unutmayın (31 bit 2 milyar olası durum verir, 32 bit bunun iki katı verir). Bu, başkalarının değiştirmeye çalıştığı PRNG'nin çok türüdür!minstd_rand
Çalışacak, ancak gerçekten hıza ihtiyacınız yoksa ve rasgelelik kalitesini umursamıyorsanız kullanmazdım (yine de rastgele nedir?). Bir oyun reçel veya bir demo ya da bir şey için harika. LCG'ler tohum korelasyonlarından muzdariptir, bu nedenle bir LCG'nin ilk sonucunu atmak en iyisidir . Ve bir LCG kullanmakta ısrar ederseniz, bir artış değeri eklemek sonuçları iyileştirebilir, ancak muhtemelen daha iyi seçenekler olduğunda boşuna bir egzersizdir.
32 bit durum (artan durum alanı) sunan başka çarpanlar var gibi görünüyor:
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
Bu LCG değerleri şöyledir: P. L'Ecuyer: Farklı boyutlarda ve iyi kafes yapısına sahip Doğrusal Konjügasyon Jeneratörleri tablosu, 30 Nisan 1997.
seed = (seed * 185852 + 1) % 34359738337
.
Math.imul
32 bit tamsayılarda C çarpma kullanırken olduğu gibi taşmasına izin verir. Önerdiğiniz, JS'nin tam sayı alanını kullanan bir LCG'dir, bu da kesinlikle keşfedilecek ilginç bir alandır. :)
Hayır, ancak burada basit bir sözde jeneratör, Wikipedia'dan uyarlanmış taşıma ile çarpma uygulaması (o zamandan beri kaldırıldı):
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: sıfırlama yaparak sabit tohum işlevi m_z
EDIT2: Ciddi uygulama kusurları giderildi
seed
Çünkü fonksiyon, rasgele jeneratör sıfırlanmaz mz_z
zaman değişken değiştirilir random()
denir. Bu nedenlemz_z = 987654321
seed
m_w
, değil m_z
. 2) Her ikisi de m_w
ve m_z
önceki değerlerinde BASED değerini değiştirir, böylece sonucu değiştirir.
Antti Sykäri'nin algoritması hoş ve kısa. Başlangıçta Math.seed (s) çağırdığınızda Javascript Math.random yerine bir varyasyon yaptım, ama sonra Jason işlevi dönen daha iyi olacağını yorumladı:
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());
Bu, Javascript'te bulunmayan başka bir işlevsellik sağlar: birden fazla bağımsız rastgele jeneratör. Aynı anda birden fazla tekrarlanabilir simülasyon yapmak istiyorsanız bu özellikle önemlidir.
Math.random
fazla bağımsız jeneratöre sahip olmanızı sağlayacak bir ayar yerine işlevi döndürürseniz , değil mi?
Math.seed(42);
bunu yaparsanız o fonksiyonu sıfırlar, böylece var random = Math.seed(42); random(); random();
almak 0.70...
, sonra 0.38...
. var random = Math.seed(42);
Tekrar arayarak sıfırlarsanız , bir daha aradığınızda tekrar random()
alırsınız 0.70...
ve bir dahaki sefere 0.38...
tekrar alırsınız .
random
bir javascript işlevinin üzerine yazmak yerine adlı bir yerel değişkeni kullanın. Üzerine yazma Math.random
işlemi JIST derleyicisinin tüm kodunuzu açmamasına neden olabilir.
Lütfen Pierre L'Ecuyer'in 1980'lerin sonlarına ve 1990'ların başına kadar olan çalışmalarına bakın. Başkaları da var. Bir uzman değilseniz, kendi başına (sözde) rasgele bir sayı üreteci oluşturmak oldukça tehlikelidir, çünkü sonuçların istatistiksel olarak rasgele olmaması veya küçük bir döneme sahip olma olasılığı yüksektir. Pierre (ve diğerleri) uygulaması kolay olan bazı iyi (sözde) rasgele sayı üreteçlerini bir araya getirmiştir. LFSR jeneratörlerinden birini kullanıyorum.
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
Phil Troy
Önceki cevaplardan bazılarını birleştirdiğinizde, aradığınız görülebilir rastgele işlev budur:
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)()
döndürür 0.2322845458984375
ve Math.seed(1)()
döndürür 0.23228873685002327
. Her ikisini de değiştirmek m_w
ve m_z
tohuma göre yardımcı olur. var m_w = 987654321 + s; var m_z = 123456789 - s;
farklı tohumlarla ilk değerlerin güzel dağılımını üretir.
Kendi sözde rastgele jeneratör yazmak oldukça basittir.
Dave Scotese'nin önerisi faydalıdır, ancak başkalarının da işaret ettiği gibi, oldukça eşit dağılmamıştır.
Ancak bunun nedeni günahın tamsayı argümanları değildir. Bunun nedeni, bir dairenin tek boyutlu bir projeksiyonu olan günah aralığıdır. Çemberin açısını alırsanız, üniform olur.
Sin (x) yerine arg (exp (i * x)) / (2 * PI) kullanın.
Doğrusal düzeni sevmiyorsanız, xor ile biraz karıştırın. Gerçek faktör de o kadar önemli değil.
N sözde rastgele sayılar üretmek için kod kullanılabilir:
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))
Ayrıca, gerçek entropi gerektiğinde sözde rastgele dizileri kullanamayacağınızı da unutmayın.
Bugünlerde Javascript'te görülebilen rasgele sayı üretecine ihtiyaç duyan birçok kişi David Bau'nun seedrandom modülünü kullanıyor .
Math.random
hayır, ama koşan kütüphane bunu çözer. Hayal edebileceğiniz neredeyse tüm dağıtımlara sahiptir ve tohumlanmış rastgele sayı üretimini destekler. Misal:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
Tohumlanmış rasgele bir sayı döndüren bir işlev yazdım, Math.sin uzun bir rasgele sayıya sahip kullanır ve tohum sayıları almak için kullanır.
Kullanım:
seedRandom("k9]:2@", 15)
ilk parametre herhangi bir dize değeri ise tohumlanmış numaranızı döndürür; senin tohum. ikinci parametre kaç basamak döneceğidir.
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)
}
Sabit bir tohum için basit bir yaklaşım:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
0 ile 100 arasında bir sayı için.
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
her şekilde Math.random
aynı tohum ile tohumlanır, bu rasgele sayı aynı ardışık dizi üretecektir. Bu soru, her halükarda, fiili kullanımı / gösterimi ile ilgili değildir Math.random
.