programing

시드 가능한 JavaScript 난수 생성기

subpage 2023. 9. 1. 21:00
반응형

시드 가능한 JavaScript 난수 생성기

JavaScript 함수는 0에서 1 사이의 임의의 값을 반환하며, 현재 시간을 기준으로 자동으로 시드됩니다(Java와 유사).하지만, 저는 그것을 위한 여러분만의 씨앗을 만들 방법이 없다고 생각합니다.

어떻게 하면 제 자신의 시드 값을 제공할 수 있는 난수 생성기를 만들어 반복 가능한 (의사) 난수 시퀀스를 생성할 수 있을까요?

한 가지 옵션은 시드 가능한 RC4 기반 Math.random() 드롭인 대체 프로그램인 http://davidbau.com/seedrandom 입니다.

파종 기능이 필요하지 않은 경우에는 다음을 사용합니다.Math.random()주변에 도우미 기능을 구축합니다(예:randRange(start, end)).

당신이 어떤 RNG를 사용하고 있는지는 모르겠지만, 그것의 특성과 한계를 알 수 있도록 알고 문서화하는 것이 가장 좋습니다.

스타키가 말했듯이 Mersenne Twister는 좋은 PRNG이지만 구현하기가 쉽지 않습니다.만약 당신이 그것을 직접 하고 싶다면 LCG를 구현해 보세요 - 그것은 매우 쉽고, 괜찮은 무작위 특성을 가지고 있습니다(메르센 트위스터만큼 좋지 않습니다), 그리고 당신은 인기 있는 상수들 중 일부를 사용할 수 있습니다.

편집: LCG 옵션을 포함한 짧은 시드 가능한 RNG 구현을 위한 이 답변의 좋은 옵션을 고려합니다.

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));

를 지정할 수 , 은 단지 을 시를지하하바됩면니다로 만 하면 .getSeconds()그리고.getMinutes() mod .int 을 제공할 수 .

그렇긴 하지만, 이 방법은 쓰레기처럼 보입니다.적절한 난수 생성을 하는 것은 매우 어렵습니다.이것의 명백한 문제는 난수 시드가 초와 분을 기반으로 한다는 것입니다.시드를 추측하고 난수 스트림을 다시 만들려면 3600개의 다른 초 및 분 조합만 시도하면 됩니다.이것은 또한 3600개의 다른 가능한 씨앗들만 있다는 것을 의미합니다.이것은 수정할 수 있지만, 저는 처음부터 이 RNG를 의심할 것입니다.

더 좋은 RNG를 사용하고 싶다면 Mersenne Twister를 사용해 보세요.그것은 거대한 궤도와 뛰어난 성능을 가진 잘 테스트되고 상당히 견고한 RNG입니다.

편집: 저는 정말로 이것을 의사 난수 생성기 또는 PRNG라고 불러야 합니다.

"산술로 난수를 만드는 사람은 죄악의 상태에 있습니다."

나는 Mersenne Twister의 JavaScript 포트를 사용합니다: https://gist.github.com/300494 이것은 당신이 수동으로 시드를 설정할 수 있게 해줍니다.또한 다른 답변에서도 언급했듯이 Mersenne Twister는 정말 좋은 PRNG입니다.

당신이 나열한 코드는 일종의 레머 RNG처럼 보입니다. 만약 그렇다면,2147483647가장 큰 32비트 부호 정수입니다.2147483647가장 큰 32비트 프라임입니다.48271는 숫자를 생성하는 데 사용되는 전체 주기 승수입니다.

만약 이것이 사실이라면, 당신은 수정할 수 있습니다.RandomNumberGenerator 매개 인 추가합매사다니용를변을 사용합니다.seed에 그음에다를 합니다.this.seedseed하지만 씨앗이 난수 분포를 잘 나타내도록 주의해야 할 것입니다. (레머는 그렇게 이상할 수 있습니다.) 하지만 대부분의 씨앗은 괜찮을 것입니다.

다음은 사용자 지정 시드를 제공할 수 있는 PRNG입니다.하기 르기SeedRandom임의 생성기 함수를 반환합니다.SeedRandom현재 시간으로 반환된 임의 함수를 시드하기 위해 인수 없이 호출하거나, 해당 정수로 시드하기 위해 음이 아닌 1개 또는 2개의 인터를 인수로 호출할 수 있습니다.부동 소수점 정확도로 인해 1개의 값만 사용하면 제너레이터를 2^53개의 서로 다른 상태 중 하나로만 시작할 수 있습니다.

는 반된임생개함수 1의정인라는 정수 합니다.limit한계는 1 ~ 4294965886 범위여야 하며 함수는 0 ~ 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
}

사용 예:

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.

이 제너레이터는 다음과 같은 특성을 나타냅니다.

  • 그것은 대략 2^64개의 다른 가능한 내부 상태를 가지고 있습니다.
  • 자바스크립트 프로그램에서 누구나 현실적으로 필요로 하는 것보다 훨씬 많은 약 2^63의 기간을 가집니다.
  • 에 때문에mod소수인 값은 선택한 한계에 관계없이 출력에 단순한 패턴이 없습니다.이는 상당히 체계적인 패턴을 보이는 일부 단순한 PRNG와는 다릅니다.
  • 한계에 관계없이 완벽한 분포를 얻기 위해 일부 결과를 무시합니다.
  • 그것은 제 기계에서 초당 약 10,000,000번 실행되는 비교적 느립니다.

보너스: 유형 스크립트 버전

만약 당신이 Typescript에서 프로그래밍을 한다면, 나는 이 스레드에 Christoph Henkelmann의 답변에 가져온 Mersenne Twister 구현을 Typescript 클래스로 적용했습니다.

/**
 * 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);
    }
}

다음과 같이 사용할 수 있습니다.

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]

소스에서 더 많은 메서드를 확인합니다.

여기 제가 사용하고 싶은 꽤 효과적이지만 간단한 자바스크립트 PRNG 함수가 있습니다.

// The seed is the base number that the function works off
// The modulo is the highest number that the function can return

function PRNG(seed, modulo) {
    str = `${(2**31-1&Math.imul(48271,seed))/2**31}`
    .split('')
    .slice(-10)
    .join('') % modulo

    return str
}

이것이 당신이 찾고 있는 것이기를 바랍니다.

감사합니다, @aaaaaaaaaaaaaaaa(승인된 답변)

저는 좋은 비도서관 솔루션이 정말로 필요했습니다(내장하기 더 쉬운).

그래서... 시드를 저장하고 Unity-esque "Next"를 허용하기 위해 이 클래스를 만들었지만 초기 정수 기반 결과는 유지했습니다.

class randS {
    constructor(seed=null) {
        if(seed!=null) {
            this.seed = seed;
        } else {
            this.seed = Date.now()%4645455524863;
        }
        this.next = this.SeedRandom(this.seed);
        this.last = 0;
    }
    Init(seed=this.seed) {
        if (seed = this.seed) {
            this.next = this.SeedRandom(this.seed);
        } else {
            this.seed=seed;
            this.next = this.SeedRandom(this.seed);
        }
    }
    SeedRandom(state1,state2){
        var mod1=4294967087;
        var mod2=4294965887;
        var mul1=65539;
        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){
                this.last = random;
                return random(limit);
            }
            this.last = (state1+state2)%limit;
            return (state1+state2)%limit;
        }
        this.last = random;
        return random;

    }
}

그리고 나서 이것들로 확인했습니다...무작위(그러나 쿼리 가능) 시드 값(라 마인크래프트)과 잘 작동하고 마지막으로 반환된 값(필요한 경우)도 저장합니다.

var rng = new randS(9005646549);
console.log(rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20));
console.log(rng.next(20) + ' ' + rng.next(20) + ' ' + rng.last);

출력해야 하는 항목(모두를 위한)

6 7 8 14 1 12 6
9 1 1

편집: 재시딩이 필요하거나 값을 테스트할 때 init()가 작동하도록 했습니다(이것은 내 맥락에서도 필요했습니다).

참고: 이 코드는 원래 위의 질문에 포함되어 있었습니다.질문을 짧고 집중하기 위해 커뮤니티 위키 답변으로 이동했습니다.

저는 이 코드가 여기저기서 작동하는 것을 발견했고 무작위 번호를 얻은 다음 나중에 시드를 사용하는 것에 대해 잘 작동하는 것처럼 보이지만 논리가 어떻게 작동하는지는 잘 모르겠습니다(예: 2345678901, 48271 및 2147483647 번호가 어디에서 왔는지).

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.
*/

다음 IIF는 재생 가능한 임의의 31비트 정수의 긴 시퀀스를 생성합니다.오버플로 JS 정수를 방지하기 위해 두 개의 15비트 소수를 사용합니다.

let random = (function () {
  let a = 1, b = 1;
  return {
    nextInt: function () {
      a = (a * 67307) & 0xffff;
      b = (b * 67427) & 0xffff;
      return a ^ (b << 15);
    },
    reset(seed) {
      a = b = seed | 0;
    }
  };
})();

다음 코드는 사용 방법을 보여줍니다.

random.reset(2); // Reset to start of sequence

// Log sequence of random numbers
for (let i = 0; i < 100; i++)
  console.log(random.nextInt());

좋아요, 여기 제가 결정한 해결책이 있습니다.

먼저 "new seed()" 함수를 사용하여 시드 값을 만듭니다.그런 다음 시드 값을 "랜덤()" 함수에 전달합니다.마지막으로, "랜덤()" 함수는 0과 1 사이의 의사 무작위 값을 반환합니다.

중요한 비트는 시드 값이 배열 내부에 저장된다는 것입니다.단순히 정수나 부동소수일 경우, 정수, 부동소수, 문자열 등의 값이 배열 및 기타 객체의 경우처럼 포인터만 아니라 스택에 직접 저장되므로 함수가 호출될 때마다 값이 덮어쓰게 됩니다.따라서, 씨앗의 가치가 지속적으로 유지될 수 있습니다.

마지막으로, "랜덤()" 함수를 "수학" 객체의 방법이 되도록 정의하는 것은 가능하지만, 그것은 여러분에게 맡기겠습니다. ;)

행운을 빕니다.

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(개인 목표 환경):

-- 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)

언급URL : https://stackoverflow.com/questions/424292/seedable-javascript-random-number-generator

반응형