Trình tạo số ngẫu nhiên JavaScript có thể khởi tạo


149

Hàm JavaScript Math.random()trả về giá trị ngẫu nhiên trong khoảng từ 0 đến 1, tự động được tạo mầm dựa trên thời gian hiện tại (tương tự như Java tôi tin). Tuy nhiên, tôi không nghĩ có cách nào để đặt hạt giống của riêng bạn cho nó.

Làm cách nào tôi có thể tạo một trình tạo số ngẫu nhiên mà tôi có thể cung cấp giá trị hạt giống của riêng mình để tôi có thể tạo ra một chuỗi lặp lại các số ngẫu nhiên (giả)?


1
Lưu ý: Vì lợi ích của việc giữ cho câu hỏi này ngắn gọn và tập trung, tôi đã tách mã trong câu hỏi trên thành câu trả lời của Wiki cộng đồng bên dưới.
Ilmari Karonen

Câu trả lời:


123

Một tùy chọn là http://davidbau.com/seedrandom , đây là một thay thế thả xuống Math.random () dựa trên RC4 có thể tạo hạt với các thuộc tính đẹp.


18
Seedrandom của David Bau đã trở nên phổ biến đến mức ông duy trì nó ở đây trên github . Thật là một ECMAScript xấu hổ đã trở lại quá lâu đến nỗi những thứ như thế này không được đưa vào ngôn ngữ. Nghiêm túc, không có hạt giống !!!
Ăn tại Joes

2
@EatatJoes, Đó là cả sự xấu hổ và vinh quang của JS rằng điều này là cần thiết và có thể. Thật tuyệt vời khi bạn có thể bao gồm một tệp và nhận các thay đổi tương thích ngược được thực hiện cho đối tượng Math. Không tệ trong 10 ngày làm việc, Brendan Eich.
Bruno Bronosky 11/05/2015

2
Đối với bất cứ ai đang tìm kiếm trang npm
Kip

27

Nếu bạn không cần khả năng gieo hạt, chỉ cần sử dụng Math.random()và xây dựng các hàm trợ giúp xung quanh nó (ví dụ. randRange(start, end)).

Tôi không chắc RNG bạn đang sử dụng cái gì, nhưng tốt nhất nên biết và ghi lại nó để bạn nhận thức được các đặc điểm và giới hạn của nó.

Giống như Starkii đã nói, Mersenne Twister là một PRNG tốt, nhưng nó không dễ thực hiện. Nếu bạn muốn tự mình thử thực hiện LCG - rất dễ dàng, có phẩm chất ngẫu nhiên khá (không tốt như Mersenne Twister), và bạn có thể sử dụng một số hằng số phổ biến.

EDIT: xem xét các tùy chọn tuyệt vời trong câu trả lời này cho các triển khai RNG có thể gieo hạt ngắn, bao gồm tùy chọn LCG.

function RNG(seed) {
  // LCG using GCC's constants
  this.m = 0x80000000; // 2**31;
  this.a = 1103515245;
  this.c = 12345;

  this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextInt = function() {
  this.state = (this.a * this.state + this.c) % this.m;
  return this.state;
}
RNG.prototype.nextFloat = function() {
  // returns in range [0,1]
  return this.nextInt() / (this.m - 1);
}
RNG.prototype.nextRange = function(start, end) {
  // returns in range [start, end): including start, excluding end
  // can't modulu nextInt because of weak randomness in lower bits
  var rangeSize = end - start;
  var randomUnder1 = this.nextInt() / this.m;
  return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.choice = function(array) {
  return array[this.nextRange(0, array.length)];
}

var rng = new RNG(20);
for (var i = 0; i < 10; i++)
  console.log(rng.nextRange(10, 50));

var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (var i = 0; i < 10; i++)
  console.log(rng.choice(digits));


2
Không nên mô đun là 2 ^ 31? Tôi đọc thuật toán này từ wiki .
Trantor Liu

3
Để bạn nhận thức được, điều này không "chính xác" theo nghĩa là nó không đưa ra những gì toán học ra lệnh. Nói cách khác, một ngôn ngữ có thể xử lý những con số lớn đó sẽ có kết quả khác. JS sặc về số lượng lớn và độ chính xác chops (rốt cuộc chúng là số float).
DDS

4
-1 Việc triển khai LCG này phá vỡ giới hạn cho các số nguyên chính xác trong JavaScript vì this.a * this.statecó khả năng dẫn đến một số lớn hơn 2 ^ 53. Kết quả là một phạm vi đầu ra hạn chế, và đối với một số hạt giống có thể là một khoảng thời gian rất ngắn. Nói chung, sử dụng công suất hai cho mkết quả trong một số mô hình khá rõ ràng, khi bạn đang sử dụng một hoạt động mô đun thay vì cắt ngắn đơn giản, không có lý do gì để không sử dụng một số nguyên tố.
aaaaaaaaaaaa

22

Nếu bạn muốn có thể chỉ định hạt giống, bạn chỉ cần thay thế các cuộc gọi đến getSeconds()getMinutes() . Bạn có thể chuyển vào một int và sử dụng một nửa số mod 60 cho giá trị giây và nửa còn lại modulo 60 để cung cấp cho bạn phần còn lại.

Điều đó đang được nói, phương pháp này trông giống như rác. Làm thế hệ số ngẫu nhiên thích hợp là rất khó. Vấn đề rõ ràng với điều này là hạt giống số ngẫu nhiên dựa trên giây và phút. Để đoán hạt giống và tạo lại luồng số ngẫu nhiên của bạn chỉ cần thử 3600 kết hợp giây và phút khác nhau. Nó cũng có nghĩa là chỉ có 3600 hạt giống khác nhau có thể. Điều này là chính xác, nhưng tôi nghi ngờ về RNG này ngay từ đầu.

Nếu bạn muốn sử dụng RNG tốt hơn, hãy thử Mersenne Twister . Nó là một RNG được thử nghiệm tốt và khá mạnh mẽ với quỹ đạo khổng lồ và hiệu suất tuyệt vời.

EDIT: Tôi thực sự nên chính xác và coi đây là Trình tạo số ngẫu nhiên giả hoặc PRNG.

"Bất cứ ai sử dụng các phương pháp số học để tạo ra các số ngẫu nhiên đều ở trong tình trạng tội lỗi."
                                                                                                                                                          --- John von Neumann


1
Liên kết đến các triển khai JS của Mersenne Twister: math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/
orip

1
@orip Bạn có nguồn cho 3600 trạng thái ban đầu không? Mersenne Twister được gieo bằng số 32 bit, vì vậy PRNG nên có 4 tỷ trạng thái ban đầu - chỉ khi hạt giống ban đầu thực sự ngẫu nhiên.
Tobias P.

2
@TobiasP. Tôi đã đề cập đến gợi ý để gieo hạt giống với sự kết hợp của getSeconds () và getMinutes (), 60 * 60 == 3600 trạng thái ban đầu có thể. Tôi đã không đề cập đến Mersenne Twister.
orip

3
@orip Ok, không rõ ràng. Bạn đang dùng về Mersenne Twister và trong câu tiếp theo về tình trạng bẩm sinh;)
Tobias P.

2
Người hỏi không đưa ra bất kỳ đề cập nào rằng họ cần tạo số ngẫu nhiên "phù hợp" cho bất kỳ loại ứng dụng nhạy cảm nào về mật mã. Trong khi tất cả các câu trả lời là đúng, chỉ có đoạn đầu tiên thực sự có liên quan đến câu hỏi được hỏi. Có lẽ thêm một đoạn mã của giải pháp được đề xuất.
V. Rubinetti

12

Tôi sử dụng cổng JavaScript của Mersenne Twister: https://gist.github.com/300494 Nó cho phép bạn đặt hạt giống theo cách thủ công. Ngoài ra, như đã đề cập trong các câu trả lời khác, Mersenne Twister là một PRNG thực sự tốt.


8

Mã bạn đã liệt kê trông giống như một RNG của Lehmer . Nếu đây là trường hợp, thì 2147483647số nguyên có chữ ký 214748364732 bit lớn nhất , là số nguyên tố 32 bit lớn nhất và48271 là một số nhân toàn thời gian được sử dụng để tạo ra các số.

Nếu điều này là đúng, bạn có thể sửa đổi RandomNumberGeneratorđể lấy thêm một tham số seed, rồi đặt this.seedthành seed; nhưng bạn phải cẩn thận để đảm bảo hạt giống sẽ dẫn đến sự phân phối tốt các số ngẫu nhiên (Lehmer có thể kỳ lạ như thế) - nhưng hầu hết các hạt giống sẽ ổn.


7

Sau đây là một PRNG có thể được cho ăn một hạt giống tùy chỉnh. Gọi SeedRandomsẽ trả về một hàm tạo ngẫu nhiên.SeedRandomcó thể được gọi mà không có đối số để tạo hàm ngẫu nhiên được trả về với thời gian hiện tại hoặc có thể được gọi với 1 hoặc 2 giao điểm không âm làm đối số để tạo hạt giống với các số nguyên đó. Do gieo hạt chính xác điểm nổi chỉ có 1 giá trị sẽ chỉ cho phép trình tạo được khởi tạo ở một trong 2 ^ 53 trạng thái khác nhau.

Hàm tạo ngẫu nhiên được trả về có 1 đối số nguyên được đặt tên limit, giới hạn phải nằm trong phạm vi 1 đến 4294965886, hàm sẽ trả về một số trong phạm vi 0 đến giới hạn-1.

function SeedRandom(state1,state2){
    var mod1=4294967087
    var mul1=65539
    var mod2=4294965887
    var mul2=65537
    if(typeof state1!="number"){
        state1=+new Date()
    }
    if(typeof state2!="number"){
        state2=state1
    }
    state1=state1%(mod1-1)+1
    state2=state2%(mod2-1)+1
    function random(limit){
        state1=(state1*mul1)%mod1
        state2=(state2*mul2)%mod2
        if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
            return random(limit)
        }
        return (state1+state2)%limit
    }
    return random
}

Ví dụ sử dụng:

var generator1=SeedRandom() //Seed with current time
var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
var generator2=SeedRandom(42) //Seed with a specific seed
var fixedVariable=generator2(7) //First value of this generator will always be
                                //1 because of the specific seed.

Máy phát điện này thể hiện các tính chất sau:

  • Nó có khoảng 2 ^ 64 trạng thái bên trong khác nhau có thể.
  • Nó có khoảng thời gian khoảng 2 ^ 63, nhiều hơn bất kỳ ai sẽ thực sự cần trong một chương trình JavaScript.
  • Do các modgiá trị là các số nguyên tố, không có mẫu đơn giản nào ở đầu ra, bất kể giới hạn đã chọn. Điều này không giống như một số PRNG đơn giản hơn thể hiện một số mô hình khá hệ thống.
  • Nó loại bỏ một số kết quả để có được một bản phân phối hoàn hảo bất kể giới hạn.
  • Nó tương đối chậm, chạy khoảng 10 000 000 lần mỗi giây trên máy của tôi.

2
Tại sao điều này tạo ra một mô hình? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
Timothy Kanski

@TimothyKanski Vì bạn đang sử dụng sai. Tôi không phải là chuyên gia nhưng điều này xảy ra bởi vì bạn đang khởi tạo trình tạo trên mỗi lần lặp, chỉ nhìn thấy giá trị đầu tiên của nó dựa trên hạt giống và KHÔNG lặp lại trên các số tiếp theo của trình tạo. Tôi tin rằng điều này sẽ xảy ra trong bất kỳ PRNG nào không băm hạt giống trong khoảng thời gian được chỉ định.
bryc

5

Nếu bạn lập trình trong Bản mô tả, tôi đã điều chỉnh triển khai Mersenne Twister được đưa vào câu trả lời của Christoph Henkelmann cho chủ đề này dưới dạng một lớp bản thảo:

/**
 * copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
 * all rights reserved to him.
 */
export class Random {
    static N = 624;
    static M = 397;
    static MATRIX_A = 0x9908b0df;
    /* constant vector a */
    static UPPER_MASK = 0x80000000;
    /* most significant w-r bits */
    static LOWER_MASK = 0x7fffffff;
    /* least significant r bits */

    mt = new Array(Random.N);
    /* the array for the state vector */
    mti = Random.N + 1;
    /* mti==N+1 means mt[N] is not initialized */

    constructor(seed:number = null) {
        if (seed == null) {
            seed = new Date().getTime();
        }

        this.init_genrand(seed);
    }

    private init_genrand(s:number) {
        this.mt[0] = s >>> 0;
        for (this.mti = 1; this.mti < Random.N; this.mti++) {
            var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
            this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
                + this.mti;
            /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
            /* In the previous versions, MSBs of the seed affect   */
            /* only MSBs of the array mt[].                        */
            /* 2002/01/09 modified by Makoto Matsumoto             */
            this.mt[this.mti] >>>= 0;
            /* for >32 bit machines */
        }
    }

    /**
     * generates a random number on [0,0xffffffff]-interval
     * @private
     */
    private _nextInt32():number {
        var y:number;
        var mag01 = new Array(0x0, Random.MATRIX_A);
        /* mag01[x] = x * MATRIX_A  for x=0,1 */

        if (this.mti >= Random.N) { /* generate N words at one time */
            var kk:number;

            if (this.mti == Random.N + 1)   /* if init_genrand() has not been called, */
                this.init_genrand(5489);
            /* a default initial seed is used */

            for (kk = 0; kk < Random.N - Random.M; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + Random.M] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            for (; kk < Random.N - 1; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + (Random.M - Random.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            y = (this.mt[Random.N - 1] & Random.UPPER_MASK) | (this.mt[0] & Random.LOWER_MASK);
            this.mt[Random.N - 1] = this.mt[Random.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];

            this.mti = 0;
        }

        y = this.mt[this.mti++];

        /* Tempering */
        y ^= (y >>> 11);
        y ^= (y << 7) & 0x9d2c5680;
        y ^= (y << 15) & 0xefc60000;
        y ^= (y >>> 18);

        return y >>> 0;
    }

    /**
     * generates an int32 pseudo random number
     * @param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
     * @return {number}
     */
    nextInt32(range:[number, number] = null):number {
        var result = this._nextInt32();
        if (range == null) {
            return result;
        }

        return (result % (range[1] - range[0])) + range[0];
    }

    /**
     * generates a random number on [0,0x7fffffff]-interval
     */
    nextInt31():number {
        return (this._nextInt32() >>> 1);
    }

    /**
     * generates a random number on [0,1]-real-interval
     */
    nextNumber():number {
        return this._nextInt32() * (1.0 / 4294967295.0);
    }

    /**
     * generates a random number on [0,1) with 53-bit resolution
     */
    nextNumber53():number {
        var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
        return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
    }
}

bạn có thể sử dụng nó như sau:

var random = new Random(132);
random.nextInt32(); //return a pseudo random int32 number
random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
random.nextNumber(); //return a a pseudo random number in range [0,1]

kiểm tra nguồn để biết thêm phương pháp


0

Lưu ý: Mã này ban đầu được bao gồm trong câu hỏi trên. Để giữ câu hỏi ngắn gọn và tập trung, tôi đã chuyển nó sang câu trả lời Wiki cộng đồng này.

Tôi thấy mã này hoạt động xung quanh và nó có vẻ hoạt động tốt để lấy số ngẫu nhiên và sau đó sử dụng hạt giống nhưng tôi không chắc logic hoạt động như thế nào (ví dụ: số 2345678901, 48271 & 2147483647 đến từ đâu).

function nextRandomNumber(){
  var hi = this.seed / this.Q;
  var lo = this.seed % this.Q;
  var test = this.A * lo - this.R * hi;
  if(test > 0){
    this.seed = test;
  } else {
    this.seed = test + this.M;
  }
  return (this.seed * this.oneOverM);
}

function RandomNumberGenerator(){
  var d = new Date();
  this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
  this.A = 48271;
  this.M = 2147483647;
  this.Q = this.M / this.A;
  this.R = this.M % this.A;
  this.oneOverM = 1.0 / this.M;
  this.next = nextRandomNumber;
  return this;
}

function createRandomNumber(Min, Max){
  var rand = new RandomNumberGenerator();
  return Math.round((Max-Min) * rand.next() + Min);
}

//Thus I can now do:
var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var numbers = ['1','2','3','4','5','6','7','8','9','10'];
var colors = ['red','orange','yellow','green','blue','indigo','violet'];
var first = letters[createRandomNumber(0, letters.length)];
var second = numbers[createRandomNumber(0, numbers.length)];
var third = colors[createRandomNumber(0, colors.length)];

alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");

/*
  If I could pass my own seed into the createRandomNumber(min, max, seed);
  function then I could reproduce a random output later if desired.
*/

3
Wow, RandomNumberGeneratorvà các nextRandomNumberchức năng thực sự có từ năm 1996. Nó được cho là một RNG của Lehmer / LCG. Nó sử dụng một số phép toán thông minh để thực hiện số học modulo trên các số nguyên 32 bit nếu không quá nhỏ để chứa một số giá trị trung gian. Vấn đề là, JavaScript không triển khai các số nguyên 32 bit, mà là 64 bit trôi nổi và do phép chia không phải là phép chia số nguyên như mã này cho rằng kết quả không phải là trình tạo Lehmer. Nó không tạo ra một số kết quả có vẻ ngẫu nhiên, nhưng sự đảm bảo của một trình tạo Lehmer không được áp dụng.
aaaaaaaaaaaa

1
Các createRandomNumberchức năng là một bổ sung sau, nó khá nhiều tất cả mọi thứ sai, đáng chú ý nhất nó instantiates một RNG mới mỗi khi nó được gọi, có nghĩa là các cuộc gọi liên tiếp nhanh chóng tất cả sẽ sử dụng phao tương tự. Trong mã đã cho, hầu như không 'a'thể ghép nối với bất cứ thứ gì ngoài '1''red'.
aaaaaaaaaaaa

-2

OK, đây là giải pháp tôi đã giải quyết.

Trước tiên, bạn tạo một giá trị hạt giống bằng cách sử dụng hàm "newseed ()". Sau đó, bạn chuyển giá trị hạt giống cho hàm "srandom ()". Cuối cùng, hàm "srandom ()" trả về giá trị giả ngẫu nhiên trong khoảng từ 0 đến 1.

Điều cốt yếu là giá trị hạt giống được lưu trữ bên trong một mảng. Nếu nó chỉ đơn giản là một số nguyên hoặc float, giá trị sẽ bị ghi đè mỗi khi hàm được gọi, vì các giá trị của số nguyên, số float, chuỗi và vv được lưu trữ trực tiếp trong ngăn xếp so với chỉ các con trỏ như trong trường hợp mảng và các đối tượng khác. Vì vậy, giá trị của hạt giống vẫn có thể tồn tại.

Cuối cùng, có thể định nghĩa hàm "srandom ()" sao cho nó là một phương thức của đối tượng "Toán học", nhưng tôi sẽ để bạn tự tìm hiểu. ;)

Chúc may mắn!

JavaScript:

// Global variables used for the seeded random functions, below.
var seedobja = 1103515245
var seedobjc = 12345
var seedobjm = 4294967295 //0x100000000

// Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
{
    return [seednum]
}

// Works like Math.random(), except you provide your own seed as the first argument.
function srandom(seedobj)
{
    seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
    return seedobj[0] / (seedobjm - 1)
}

// Store some test values in variables.
var my_seed_value = newseed(230951)
var my_random_value_1 = srandom(my_seed_value)
var my_random_value_2 = srandom(my_seed_value)
var my_random_value_3 = srandom(my_seed_value)

// Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
WScript.Echo(my_random_value_1)
WScript.Echo(my_random_value_2)
WScript.Echo(my_random_value_3)

Lua 4 (môi trường mục tiêu cá nhân của tôi):

-- Global variables used for the seeded random functions, below.
seedobja = 1103515.245
seedobjc = 12345
seedobjm = 4294967.295 --0x100000000

-- Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
    return {seednum}
end

-- Works like random(), except you provide your own seed as the first argument.
function srandom(seedobj)
    seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
    return seedobj[1] / (seedobjm - 1)
end

-- Store some test values in variables.
my_seed_value = newseed(230951)
my_random_value_1 = srandom(my_seed_value)
my_random_value_2 = srandom(my_seed_value)
my_random_value_3 = srandom(my_seed_value)

-- Print the values to console.
print(my_random_value_1)
print(my_random_value_2)
print(my_random_value_3)

Tái bút - Tôi chưa quen với Stack Overflow, nhưng tại sao các bài đăng không theo thứ tự thời gian?
posfan12

Hi @ posfan12 - câu trả lời cho các câu hỏi thường được liệt kê theo thứ tự "upvotes" sao cho "kem tăng lên hàng đầu". Tuy nhiên, để đảm bảo xem công bằng các câu trả lời có cùng số điểm, chúng được hiển thị theo thứ tự ngẫu nhiên. Vì đây là câu hỏi của tôi ban đầu ;-) Tôi chắc chắn sẽ kiểm tra sớm. Nếu tôi (hoặc những người khác) thấy câu trả lời này hữu ích, chúng tôi sẽ đưa ra câu trả lời và nếu tôi thấy đó là câu trả lời "đúng", bạn cũng sẽ thấy một dấu kiểm màu xanh lá cây được thêm vào câu trả lời này. - Chào mừng bạn đến với StackOverflow!
scunliffe

2
-1 Việc triển khai LCG này phá vỡ giới hạn cho các số nguyên chính xác trong JavaScript vì seedobj[0] * seedobjacó khả năng dẫn đến một số lớn hơn 2 ^ 53. Kết quả là một phạm vi đầu ra hạn chế, và đối với một số hạt giống có thể là một khoảng thời gian rất ngắn.
aaaaaaaaaaaa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.