เป็นไปได้หรือไม่ที่จะสร้างโปรแกรมสร้างตัวเลขสุ่ม (Math.random) ใน Javascript?
เป็นไปได้หรือไม่ที่จะสร้างโปรแกรมสร้างตัวเลขสุ่ม (Math.random) ใน Javascript?
คำตอบ:
หมายเหตุ: แม้จะมีความกระชับและความสง่างามที่ชัดเจน (หรือมากกว่านั้นก็ตาม) อัลกอริทึมนี้ไม่ได้มีคุณภาพสูงในแง่ของการสุ่ม ค้นหาเช่นที่อยู่ในคำตอบนี้เพื่อผลลัพธ์ที่ดีกว่า
(ดัดแปลงมาจากแนวคิดที่ฉลาดนำเสนอในความคิดเห็นเป็นคำตอบอื่น)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
คุณสามารถตั้งค่าseed
ให้เป็นตัวเลขใด ๆ เพียงแค่หลีกเลี่ยงศูนย์ (หรือหลาย ๆ แบบของ Math.PI)
ในความคิดของฉันความสง่างามของการแก้ปัญหานี้มาจากการขาดหมายเลข "วิเศษ" (นอกเหนือจาก 10,000 ซึ่งแสดงถึงจำนวนขั้นต่ำของตัวเลขที่คุณต้องทิ้งเพื่อหลีกเลี่ยงรูปแบบแปลก ๆ - ดูผลลัพธ์ที่มีค่า10 , 100 , 1,000 ) ความกะทัดรัดก็ดีเช่นกัน
มันช้ากว่า Math.random () (ประมาณ 2 หรือ 3) แต่ฉันเชื่อว่ามันเร็วพอ ๆ กับโซลูชันอื่น ๆ ที่เขียนด้วย JavaScript
ฉันใช้งานตัวสร้างPseudorandom number (PRNG) จำนวนสั้น ๆ และรวดเร็วใน JavaScript ธรรมดา พวกเขาทั้งหมดสามารถเพาะและให้ตัวเลขที่มีคุณภาพดี
ก่อนอื่นให้ระมัดระวังในการเริ่มต้น PRNG ของคุณอย่างถูกต้อง เครื่องกำเนิดไฟฟ้าส่วนใหญ่ด้านล่างไม่มีกระบวนการสร้างเมล็ดพันธุ์ในตัว (เพื่อความเรียบง่าย) แต่ยอมรับค่า 32- บิตอย่างน้อยหนึ่งค่าเป็นสถานะเริ่มต้นของ PRNG เมล็ดที่คล้ายกัน (เช่นเมล็ดที่เรียบง่ายของ 1 และ 2) สามารถทำให้เกิดความสัมพันธ์ใน PRNG ที่อ่อนแอกว่าซึ่งส่งผลให้ผลผลิตมีคุณสมบัติที่คล้ายกัน (เช่นระดับที่สร้างแบบสุ่มจะคล้ายกัน) เพื่อหลีกเลี่ยงปัญหานี้เป็นวิธีที่ดีที่สุดในการเริ่มต้น PRNG ด้วยเมล็ดที่กระจายอย่างดี
โชคดีที่ฟังก์ชันแฮชดีในการสร้างเมล็ดสำหรับ PRNG จากสตริงสั้น ๆ ฟังก์ชันแฮชที่ดีจะสร้างผลลัพธ์ที่แตกต่างกันมากแม้ว่าจะมีสองสายเหมือนกันก็ตาม นี่คือตัวอย่างจากฟังก์ชั่นการผสมของ 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;
}
}
แต่ละโทรตามมากับฟังก์ชั่นการกลับมาของxmur3
ผลิตใหม่ "สุ่ม" ค่าแฮ 32 บิตเพื่อนำไปใช้เป็นเมล็ดพันธุ์ใน PRNG ได้ นี่คือวิธีที่คุณจะใช้:
// 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();
อีกวิธีหนึ่งคือเลือกข้อมูลจำลองเพื่อรองเมล็ดด้วยและเลื่อนตัวสร้างขึ้นสองสามครั้ง (การทำซ้ำ 12-20 ครั้ง) เพื่อผสมสถานะเริ่มต้นให้ละเอียด นี่มักจะเห็นในการใช้งานอ้างอิงของ PRNGs แต่จะ จำกัด จำนวนของสถานะเริ่มต้น
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();
ผลลัพธ์ของฟังก์ชั่น PRNG เหล่านี้จะสร้างจำนวนบวก 32- บิต (0 ถึง 2 32 -1) ซึ่งจะถูกแปลงเป็นตัวเลขทศนิยมระหว่าง 0-1 (รวม 0, 1 พิเศษ) เทียบเท่ากับMath.random()
ถ้าคุณต้องการตัวเลขสุ่ม ในช่วงที่เฉพาะเจาะจงให้อ่านบทความนี้ใน MDN หากคุณต้องการบิตดิบเพียงแค่ลบการดำเนินการหารสุดท้าย
สิ่งที่ควรทราบอีกประการหนึ่งคือข้อ จำกัด ของ JS ตัวเลขสามารถแสดงจำนวนเต็มทั้งหมดได้สูงสุดที่ความละเอียด 53 บิต และเมื่อใช้การดำเนินการระดับบิตค่านี้จะลดลงเป็น 32 ทำให้ยากที่จะใช้อัลกอริธึมที่เขียนใน C หรือ C ++ ซึ่งใช้ตัวเลข 64 บิต การย้ายรหัส 64 บิตต้องใช้ shims ที่สามารถลดประสิทธิภาพได้อย่างมาก ดังนั้นเพื่อความเรียบง่ายและมีประสิทธิภาพฉันจึงพิจารณาเฉพาะอัลกอริธึมที่ใช้คณิตศาสตร์แบบ 32 บิตเนื่องจากมันเข้ากันได้กับ JS โดยตรง
ตอนนี้เป็นต้นไปที่เครื่องกำเนิดไฟฟ้า (ฉันเก็บรักษารายการทั้งหมดไว้พร้อมอ้างอิงที่นี่ )
sfc32เป็นส่วนหนึ่งของชุดทดสอบหมายเลขแบบสุ่มPractRand (ซึ่งผ่านการทดสอบแล้ว) sfc32 มีสถานะ 128 บิตและเร็วมากใน 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 เป็นเครื่องกำเนิดไฟฟ้าอย่างง่ายที่มีสถานะ 32 บิต แต่เร็วมากและมีคุณภาพดี (ผู้เขียนระบุว่ามันผ่านการทดสอบทั้งหมดของชุดการทดสอบgjrandและมีช่วงเต็มรูปแบบ 2 32แต่ฉันยังไม่ได้ยืนยัน)
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;
}
}
ฉันจะแนะนำสิ่งนี้หากคุณต้องการเพียงPRNG ที่เรียบง่าย แต่เหมาะสมและไม่ต้องการตัวเลขสุ่มนับพันล้าน (ดูปัญหาวันเกิด )
ตั้งแต่เดือนพฤษภาคมปี 2018 xoshiro128 **เป็นสมาชิกใหม่ของตระกูล Xorshiftโดย Vigna / Blackman (ผู้เขียน xoroshiro ซึ่งใช้ใน Chrome ด้วย) มันเป็นเครื่องกำเนิดที่เร็วที่สุดที่ให้สถานะ 128 บิต
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;
}
}
ผู้เขียนอ้างว่าผ่านการทดสอบแบบสุ่มได้ดี ( แม้ว่าจะมีคำเตือน ) นักวิจัยคนอื่น ๆ ชี้ให้เห็นว่าล้มเหลวในการทดสอบใน TestU01 (โดยเฉพาะ LinearComp และ BinaryRank) ในทางปฏิบัติมันไม่ควรทำให้เกิดปัญหาเมื่อมีการใช้ลอยตัว (เช่นการใช้งานเหล่านี้) แต่อาจทำให้เกิดปัญหาหากใช้บิตเรตต่ำ
นี่คือ JSF หรือ 'smallprng' โดยบ๊อบเจนกินส์ (2007), คนที่ทำISAACและSpookyHash มันผ่านการทดสอบ PractRand และควรจะค่อนข้างเร็วแม้ว่าจะไม่เร็วเท่า 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 เป็นอย่างมากที่ง่ายและรวดเร็ว แต่คุณภาพของการสุ่มของมันเป็นอย่างต่ำที่ใช้ที่ไม่เหมาะสมจริงสามารถทำให้เกิดข้อบกพร่องในโปรแกรมของคุณ! อย่างไรก็ตามมันดีกว่าคำตอบบางอย่างที่แนะนำให้ใช้Math.sin
หรือMath.PI
! มันเป็นหนึ่งในสายการบินที่ดี :)
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
การดำเนินการนี้เรียกว่ามาตรฐานน้อยที่สุด RNG ที่เสนอโดยปาร์คมิลเลอร์ในปี 1988 และ 1993และดำเนินการใน C ++ minstd_rand
11 โปรดทราบว่าสถานะเป็น 31 บิต (31 บิตให้สถานะเป็นไปได้ 2 พันล้านรัฐและ 32 บิตมอบเป็นสองเท่า) นี่คือ PRNG ประเภทที่ผู้อื่นพยายามแทนที่!
มันจะใช้งานได้ แต่ฉันจะไม่ใช้มันถ้าคุณไม่ต้องการความเร็วและไม่สนใจคุณภาพของการสุ่ม ยอดเยี่ยมสำหรับเกมติดขัดหรือตัวอย่างหรืออะไรบางอย่าง LCG ต้องทนทุกข์จากสหสัมพันธ์ของเมล็ดดังนั้นจึงเป็นการดีที่สุดที่จะทิ้งผลแรกของ LCG และถ้าคุณยืนยันในการใช้ LCG การเพิ่มค่าที่เพิ่มขึ้นอาจปรับปรุงผลลัพธ์ แต่อาจเป็นการออกกำลังกายที่ไร้ประโยชน์เมื่อมีตัวเลือกที่ดีกว่ามาก
ดูเหมือนว่าจะมีตัวคูณอื่น ๆ เสนอรัฐ 32 บิต (เพิ่มพื้นที่รัฐ):
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
ค่า LCG เหล่านี้มาจาก: P. L'Ecuyer: ตารางของเครื่องกำเนิดไฟฟ้าเชิงเส้นตรงขนาดต่าง ๆ และโครงสร้างโครงตาข่ายที่ดีวันที่ 30 เมษายน 1997
seed = (seed * 185852 + 1) % 34359738337
ผมคิดว่าค่าต่อไปนี้มาจากหนังสือของปิแอร์มีระยะเวลาที่ใหญ่ที่สุดภายในวงเงินพื้นเมือง:
Math.imul
ช่วยให้มันล้นเช่นเดียวกับเมื่อใช้การคูณใน C กับจำนวนเต็ม 32 บิต สิ่งที่คุณแนะนำคือ LCG ใช้พื้นที่เต็มจำนวนเต็มของ JS ซึ่งเป็นพื้นที่ที่น่าสนใจในการสำรวจเช่นกัน :)
ไม่ แต่นี่เป็นเครื่องกำเนิดไฟฟ้าแบบหลอกง่ายการใช้Multiply-with-carry ที่ฉันดัดแปลงมาจากWikipedia (ถูกลบไปแล้ว):
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;
}
แก้ไข: ฟังก์ชั่นเมล็ดคงที่โดยทำให้มันรีเซ็ต m_z
EDIT2: ข้อบกพร่องการใช้งานที่จริงจังได้รับการแก้ไข
seed
ฟังก์ชั่นไม่รีเซ็ตเครื่องกำเนิดไฟฟ้าแบบสุ่มเพราะmz_z
ตัวแปรเปลี่ยนไปเมื่อrandom()
ถูกเรียกว่า ดังนั้นจึงตั้งค่าmz_z = 987654321
(หรือค่าอื่น ๆ ) ในseed
m_w
m_z
2) ทั้งสองm_w
และm_z
เปลี่ยนแปลง BASED จากค่าก่อนหน้าดังนั้นจึงแก้ไขผลลัพธ์
อัลกอริทึมของ Antti Sykäriนั้นดีและสั้น ตอนแรกฉันสร้างรูปแบบที่แทนที่ Math.random ของ Javascript เมื่อคุณเรียก Math.seed (s) แต่แล้ว Jason ให้ความเห็นว่าการคืนค่าฟังก์ชันจะดีกว่า:
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());
นี่เป็นอีกฟังก์ชั่นที่ Javascript ไม่มี: เครื่องกำเนิดไฟฟ้าสุ่มอิสระ นี่เป็นสิ่งสำคัญอย่างยิ่งหากคุณต้องการให้มีการจำลองที่สามารถทำซ้ำได้หลายครั้งพร้อมกัน
Math.random
ที่จะให้คุณมีเครื่องกำเนิดไฟฟ้าอิสระหลายเครื่องใช่ไหม
Math.seed(42);
มันรีเซ็ตฟังก์ชั่นดังนั้นหากคุณไม่var random = Math.seed(42); random(); random();
คุณจะได้รับแล้ว0.70...
0.38...
หากคุณรีเซ็ตโดยการโทรvar random = Math.seed(42);
อีกครั้งในครั้งต่อไปที่คุณโทรrandom()
คุณจะได้รับ0.70...
อีกครั้งและครั้งต่อไปที่คุณจะได้รับ0.38...
อีกครั้ง
random
แทนการเขียนทับฟังก์ชันจาวาสคริปต์ดั้งเดิม การเขียนทับMath.random
อาจทำให้คอมไพเลอร์ JIST ยกเลิกการเพิ่มโค้ดทั้งหมดของคุณ
โปรดดูผลงานของ Pierre L'Ecuyer ที่ย้อนกลับไปในช่วงปลายทศวรรษ 1980 และต้นปี 1990 มีคนอื่นเช่นกัน การสร้างตัวสร้างตัวเลขสุ่ม (หลอก) ด้วยตัวคุณเองหากคุณไม่ใช่ผู้เชี่ยวชาญนั้นค่อนข้างอันตรายเพราะมีความเป็นไปได้สูงที่ผลลัพธ์ไม่ถูกสุ่มแบบสถิติหรือมีระยะเวลาน้อย ปิแอร์ (และคนอื่น ๆ ) ได้รวมตัวสร้างตัวเลขสุ่ม (หลอก) ที่ดีซึ่งใช้งานง่าย ฉันใช้เครื่องกำเนิดไฟฟ้า LFSR ของเขา
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
Phil Troy
เมื่อรวมคำตอบก่อนหน้าเข้าด้วยกันนี่เป็นฟังก์ชั่นแบบสุ่มที่คุณสามารถค้นหาได้:
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)()
ผลตอบแทน0.2322845458984375
และผลตอบแทนMath.seed(1)()
0.23228873685002327
การเปลี่ยนทั้งสองm_w
และm_z
ตามเมล็ดดูเหมือนว่าจะช่วย var m_w = 987654321 + s; var m_z = 123456789 - s;
สร้างการกระจายที่ดีของค่าแรกด้วยเมล็ดที่แตกต่างกัน
ในการเขียนตัวกำเนิดสุ่มเทียมของคุณเองนั้นค่อนข้างง่าย
ข้อเสนอแนะของ Dave Scotese นั้นมีประโยชน์ แต่ตามที่คนอื่น ๆ ชี้ให้เห็นมันไม่ได้กระจายอย่างสม่ำเสมอ
อย่างไรก็ตามมันไม่ได้เป็นเพราะข้อโต้แย้งจำนวนเต็มของความบาป มันเป็นเพราะช่วงของความบาปที่เกิดขึ้นจากการฉายภาพหนึ่งมิติของวงกลม ถ้าคุณใช้มุมของวงกลมแทนมันจะเหมือนกัน
ดังนั้นแทนที่จะใช้ sin (x) ให้ใช้ arg (exp (i * x)) / (2 * PI)
หากคุณไม่ชอบคำสั่งเชิงเส้นให้ผสมกับ xor สักหน่อย ปัจจัยที่เกิดขึ้นจริงไม่ได้สำคัญมากเช่นกัน
เพื่อสร้างตัวเลขสุ่มหลอกคนหนึ่งสามารถใช้รหัส:
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))
โปรดทราบว่าคุณไม่สามารถใช้ลำดับสุ่มหลอกได้เมื่อจำเป็นต้องใช้เอนโทรปีของจริง
หลายคนที่จำเป็นต้องมีเครื่องกำเนิดไฟฟ้าแบบสุ่มจำนวน seedable ใน Javascript วันนี้จะใช้โมดูล seedrandom เดวิด Bau ของ
Math.random
ไม่มี แต่วิ่งห้องสมุดแก้นี้ มันมีการแจกแจงเกือบทั้งหมดที่คุณสามารถจินตนาการและสนับสนุนการสร้างหมายเลขสุ่มที่มีเมล็ด ตัวอย่าง:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
ฉันได้เขียนฟังก์ชั่นที่ส่งกลับค่าการสุ่มจำนวน seeded มันใช้ Math.sin เพื่อให้มีตัวเลขสุ่มยาวและใช้เมล็ดเพื่อเลือกหมายเลขจากที่
การใช้:
seedRandom("k9]:2@", 15)
มันจะคืนค่าจำนวน seeded ของคุณพารามิเตอร์แรกคือค่าสตริงใด ๆ เมล็ดของคุณ พารามิเตอร์ที่สองคือจำนวนหลักที่จะส่งกลับ
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)
}
วิธีการง่ายๆสำหรับเมล็ดที่ตายตัว:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
สำหรับตัวเลขระหว่าง 0 ถึง 100
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
เช่นว่าเมื่อใดก็ตามที่Math.random
มีการเพาะเมล็ดด้วยเมล็ดเดียวกันมันจะผลิตอนุกรมสุ่มตามลำดับ คำถามนี้ไม่ได้ต่อว่าเรื่องที่เกิดขึ้นจริงการใช้งาน / Math.random
การสาธิตของ