Có thể chọn trình tạo số ngẫu nhiên (Math.random) trong Javascript không?
Có thể chọn trình tạo số ngẫu nhiên (Math.random) trong Javascript không?
Câu trả lời:
Không, nó không phải, nhưng khá dễ dàng để viết trình tạo của riêng bạn, hoặc tốt hơn là sử dụng một trình tạo hiện có. Thủ tục thanh toán: câu hỏi liên quan này .
Ngoài ra, xem blog của David Bau để biết thêm thông tin về gieo hạt .
LƯU Ý: Mặc dù (hoặc đúng hơn, vì) cô đọng và thanh lịch rõ ràng, thuật toán này không có nghĩa là một thuật toán chất lượng cao về tính ngẫu nhiên. Hãy tìm ví dụ như những người được liệt kê trong câu trả lời này để có kết quả tốt hơn.
(Ban đầu được điều chỉnh từ một ý tưởng thông minh được trình bày trong một bình luận cho một câu trả lời khác.)
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
Bạn có thể đặt thành seed
bất kỳ số nào, chỉ cần tránh số không (hoặc bất kỳ bội số nào của Math.PI).
Theo tôi, sự thanh lịch của giải pháp này xuất phát từ việc không có bất kỳ số "ma thuật" nào (ngoài 10000, đại diện cho số lượng chữ số tối thiểu bạn phải vứt đi để tránh các mẫu lẻ - xem kết quả với các giá trị 10 , 100 , 1000 ). Brevity cũng tốt
Nó chậm hơn một chút so với Math.random () (theo hệ số 2 hoặc 3), nhưng tôi tin rằng nó nhanh như bất kỳ giải pháp nào khác được viết bằng JavaScript.
Tôi đã triển khai một số trình tạo số Pseudorandom tốt, ngắn và nhanh hàm (PRNG) trong JavaScript đơn giản. Tất cả chúng có thể được gieo hạt và cung cấp số lượng chất lượng tốt.
Trước hết, hãy cẩn thận để khởi tạo PRNG của bạn đúng cách. Hầu hết các trình tạo bên dưới không có quy trình tạo hạt tích hợp (vì đơn giản), nhưng chấp nhận một hoặc nhiều giá trị 32 bit làm trạng thái ban đầu của PRNG. Các hạt tương tự (ví dụ: hạt giống đơn giản là 1 và 2) có thể gây ra mối tương quan trong các PRNG yếu hơn, dẫn đến đầu ra có các thuộc tính tương tự (chẳng hạn như các mức được tạo ngẫu nhiên là tương tự nhau). Để tránh điều này, cách tốt nhất là khởi tạo PRNG với một hạt giống được phân phối tốt.
Rất may, các hàm băm rất tốt trong việc tạo hạt giống cho PRNG từ các chuỗi ngắn. Hàm băm tốt sẽ tạo ra kết quả rất khác nhau ngay cả khi hai chuỗi tương tự nhau. Đây là một ví dụ dựa trên chức năng trộn của 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;
}
}
Mỗi lệnh gọi tiếp theo đến hàm trả về xmur3
tạo ra giá trị băm 32 bit "ngẫu nhiên" mới sẽ được sử dụng làm hạt giống trong PRNG. Đây là cách bạn có thể sử dụng nó:
// 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();
Ngoài ra, chỉ cần chọn một số dữ liệu giả để đệm hạt giống và tiến bộ tạo một vài lần (12-20 lần lặp) để trộn kỹ trạng thái ban đầu. Điều này thường thấy trong các triển khai PRNG tham chiếu, nhưng nó giới hạn số lượng trạng thái ban đầu.
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();
Đầu ra của các hàm PRNG này tạo ra số 32 bit dương (0 đến 2 32 -1), sau đó được chuyển đổi thành số dấu phẩy động trong khoảng 0-1 (bao gồm 0, loại trừ 1) tương đương Math.random()
, nếu bạn muốn số ngẫu nhiên của một phạm vi cụ thể, đọc bài viết này trên MDN . Nếu bạn chỉ muốn các bit thô, chỉ cần loại bỏ hoạt động phân chia cuối cùng.
Một điều cần lưu ý là những hạn chế của JS. Các số chỉ có thể biểu thị toàn bộ số nguyên có độ phân giải lên tới 53 bit. Và khi sử dụng các thao tác bitwise, điều này đã giảm xuống còn 32. Điều này gây khó khăn cho việc triển khai các thuật toán được viết bằng C hoặc C ++, sử dụng các số 64 bit. Chuyển mã 64 bit yêu cầu các miếng chêm có thể làm giảm đáng kể hiệu năng. Vì vậy, để đơn giản và hiệu quả, tôi chỉ xem xét các thuật toán sử dụng toán 32 bit, vì nó tương thích trực tiếp với JS.
Bây giờ, trở đi đến các máy phát điện. (Tôi duy trì danh sách đầy đủ với các tài liệu tham khảo ở đây )
sfc32 là một phần của bộ kiểm tra số ngẫu nhiên PracticeRand (tất nhiên là nó vượt qua). sfc32 có trạng thái 128 bit và rất nhanh trong 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 là một trình tạo đơn giản với trạng thái 32 bit, nhưng cực kỳ nhanh và có chất lượng tốt (tác giả cho biết nó vượt qua tất cả các thử nghiệm của bộ thử nghiệm gjrand và có thời gian 2 32 đầy đủ , nhưng tôi chưa xác minh).
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;
}
}
Tôi muốn giới thiệu điều này nếu bạn chỉ cần một PRNG đơn giản nhưng đàng hoàng và không cần hàng tỷ số ngẫu nhiên (xem vấn đề Sinh nhật ).
Kể từ tháng 5 năm 2018, xoshiro128 ** là thành viên mới của gia đình Xorshift , bởi Vigna / Blackman (người cũng đã viết xoroshiro, được sử dụng trong Chrome). Nó là trình tạo nhanh nhất cung cấp trạng thái 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;
}
}
Các tác giả tuyên bố nó vượt qua các bài kiểm tra ngẫu nhiên tốt ( mặc dù có cảnh báo ). Các nhà nghiên cứu khác đã chỉ ra rằng thất bại một số thử nghiệm trong TestU01 (đặc biệt là linearComp và BinaryRank). Trong thực tế, nó không gây ra vấn đề khi sử dụng float (chẳng hạn như các triển khai này), nhưng có thể gây ra vấn đề nếu dựa vào các bit thấp thô.
Đây là JSF hoặc 'smallprng' của Bob Jenkins (2007), anh chàng đã tạo ra ISAAC và SpookyHash . Nó vượt qua các bài kiểm tra PracticeRand và sẽ khá nhanh, mặc dù không nhanh như 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 cực kỳ nhanh chóng và đơn giản, nhưng chất lượng ngẫu nhiên của nó rất thấp, việc sử dụng không đúng cách thực sự có thể gây ra lỗi trong chương trình của bạn! Tuy nhiên, nó tốt hơn đáng kể so với một số câu trả lời gợi ý sử dụng Math.sin
hoặc Math.PI
! Đó là một lót mặc dù, đó là tốt đẹp :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
Việc triển khai này được gọi là RNG tiêu chuẩn tối thiểu theo đề xuất của Park House Miller vào năm 1988 & 1993 và được thực hiện trong C ++ 11 như minstd_rand
. Hãy nhớ rằng trạng thái là 31 bit (31 bit cho 2 tỷ trạng thái có thể, 32 bit cho gấp đôi số đó). Đây là loại PRNG mà những người khác đang cố gắng thay thế!
Nó sẽ hoạt động, nhưng tôi sẽ không sử dụng nó trừ khi bạn thực sự cần tốc độ và không quan tâm đến chất lượng ngẫu nhiên (dù sao thì ngẫu nhiên là gì?). Tuyệt vời cho một trò chơi kẹt hoặc một bản demo hoặc một cái gì đó. LCG chịu các tương quan hạt giống, vì vậy tốt nhất là loại bỏ kết quả đầu tiên của LCG. Và nếu bạn khăng khăng sử dụng LCG, việc thêm giá trị gia tăng có thể cải thiện kết quả, nhưng có lẽ đó là một bài tập vô ích khi tồn tại nhiều lựa chọn tốt hơn.
Dường như có các bội số khác cung cấp trạng thái 32 bit (tăng không gian trạng thái):
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
Các giá trị LCG này là từ: P. Bencuyer: Một bảng gồm các bộ tạo cộng tuyến tuyến có kích thước khác nhau và cấu trúc mạng tốt, ngày 30 tháng 4 năm 1997.
seed = (seed * 185852 + 1) % 34359738337
.
Math.imul
cho phép nó tràn như khi sử dụng phép nhân trong C trên các số nguyên 32 bit. Những gì bạn đang đề xuất là một LCG sử dụng toàn bộ không gian số nguyên của JS, đây chắc chắn là một khu vực thú vị để khám phá. :)
Không, nhưng đây là một trình tạo giả ngẫu nhiên đơn giản, việc triển khai Multiply-with-carry I được điều chỉnh từ Wikipedia (đã bị xóa từ đó):
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: chức năng seed cố định bằng cách đặt lại m_z
EDIT2: Lỗi
thực thi nghiêm trọng đã được sửa
seed
chức năng không reset máy phát ngẫu nhiên, bởi vì các mz_z
biến được thay đổi khi random()
được gọi. Do đó, đặt mz_z = 987654321
(hoặc bất kỳ giá trị nào khác) vàoseed
m_w
, không m_z
. 2) Cả hai m_w
và m_z
được thay đổi DỰA trên các giá trị trước đó của chúng, do đó, nó sẽ sửa đổi kết quả.
Thuật toán của Antti Sykäri rất hay và ngắn. Ban đầu tôi đã thực hiện một biến thể thay thế Math.random của Javascript khi bạn gọi Math.seed (s), nhưng sau đó Jason nhận xét rằng trả về hàm sẽ tốt hơn:
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());
Điều này cung cấp cho bạn một chức năng khác mà Javascript không có: nhiều trình tạo ngẫu nhiên độc lập. Điều đó đặc biệt quan trọng nếu bạn muốn có nhiều mô phỏng lặp lại chạy cùng một lúc.
Math.random
sẽ cho phép bạn có nhiều trình tạo độc lập, phải không?
Math.seed(42);
nó sẽ thiết lập lại chức năng, vì vậy nếu var random = Math.seed(42); random(); random();
bạn nhận được 0.70...
, thì 0.38...
. Nếu bạn đặt lại bằng cách gọi var random = Math.seed(42);
lại, thì lần sau bạn gọi random()
bạn sẽ nhận được0.70...
lại và lần sau bạn sẽ nhận 0.38...
lại.
random
thay vì ghi đè một hàm javascript gốc. Ghi đè Math.random
có thể khiến trình biên dịch JIST không thể tối đa hóa tất cả mã của bạn.
Xin vui lòng xem công việc của Pierre Giancuyer trở lại vào cuối những năm 1980 và đầu những năm 1990. Có những người khác là tốt. Tự tạo một trình tạo số ngẫu nhiên (giả), nếu bạn không phải là chuyên gia, thì khá nguy hiểm, vì có nhiều khả năng kết quả không ngẫu nhiên về mặt thống kê hoặc trong một khoảng thời gian nhỏ. Pierre (và những người khác) đã kết hợp một số trình tạo số ngẫu nhiên (giả) tốt, dễ thực hiện. Tôi sử dụng một trong những máy phát LFSR của anh ấy.
https://www.iro.umontreal.ca/~lecuyer/myftp/ con / handstat.pdf
Phil Troy
Kết hợp một số câu trả lời trước đây, đây là hàm ngẫu nhiên có thể tìm được mà bạn đang tìm kiếm:
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)()
trả về 0.2322845458984375
và Math.seed(1)()
trả về 0.23228873685002327
. Thay đổi cả hai m_w
và m_z
theo hạt giống dường như giúp đỡ. var m_w = 987654321 + s; var m_z = 123456789 - s;
tạo ra một phân phối tốt đẹp của các giá trị đầu tiên với các hạt giống khác nhau.
Để viết trình tạo ngẫu nhiên giả của riêng bạn khá đơn giản.
Gợi ý của Dave Scotese rất hữu ích nhưng, như được chỉ ra bởi những người khác, nó không được phân phối đồng đều.
Tuy nhiên, nó không phải là do các đối số nguyên của tội lỗi. Nó đơn giản là vì phạm vi tội lỗi, đó là hình chiếu một chiều của một vòng tròn. Nếu bạn lấy góc của hình tròn thì nó sẽ đồng nhất.
Vì vậy, thay vì sin (x) sử dụng arg (exp (i * x)) / (2 * PI).
Nếu bạn không thích thứ tự tuyến tính, hãy trộn nó lên một chút với xor. Yếu tố thực tế cũng không thành vấn đề.
Để tạo n số ngẫu nhiên giả, người ta có thể sử dụng mã:
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))
Cũng xin lưu ý rằng bạn không thể sử dụng các chuỗi ngẫu nhiên giả khi cần entropy thực.
Nhiều người cần một trình tạo số ngẫu nhiên có thể tạo hạt giống trong Javascript hiện nay đang sử dụng mô đun seedrandom của David Bau .
Tôi đã viết một hàm trả về một số ngẫu nhiên có hạt, nó sử dụng Math.sin để có một số ngẫu nhiên dài và sử dụng hạt giống để chọn các số từ đó.
Sử dụng :
seedRandom("k9]:2@", 15)
nó sẽ trả về số đã gieo của bạn, tham số đầu tiên là bất kỳ giá trị chuỗi nào; hạt giống của bạn. tham số thứ hai là có bao nhiêu chữ số sẽ trả về.
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)
}
Một cách tiếp cận đơn giản cho một hạt giống cố định:
function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}
Đối với một số từ 0 đến 100.
Number.parseInt(Math.floor(Math.random() * 100))
Math.random
sao cho bất cứ khi nào Math.random
được gieo cùng một hạt giống, nó sẽ tạo ra một chuỗi các số ngẫu nhiên liên tiếp giống nhau. Câu hỏi này không phải là về việc sử dụng / trình diễn thực tế củaMath.random
.