자바 스크립트에서 난수 생성기 시드


372

Javascript에서 난수 생성기 (Math.random)를 시드 할 수 있습니까?


다른 테스트 실행에서 동일한 결과를 반복적으로 얻을 수 있는지 또는 사용간에 무작위성을 향상시키기 위해 사용자별로 '고유 한 것'으로 시드할지 여부는 확실하지 않습니다.
simbo1905

2
아니요, 불행히도 불가능합니다. jsrand시드 가능한 PRNG가 필요할 때 작성한 작은 라이브러리입니다. 인터넷 검색을위한 더 복잡한 다른 라이브러리도 있습니다.
Domenico De Felice

4
질문에 추가 : PRNG를 제공 할 수단없이 PRNG를 제공하는 것이 어떻게 좋은 생각입니까? 이것에 대한 정당한 이유가 있습니까?
Alan

답변:



159

참고 : 간결함과 명백한 우아함에도 불구하고 (또는 오히려) 우아함은 결코 임의성 측면에서 고품질 알고리즘이 아닙니다. 더 나은 결과 를 위해이 답변 에 나와있는 것을 찾아보십시오 .

(원래 다른 답변에 대한 의견에 제시된 영리한 아이디어로 수정되었습니다.)

var seed = 1;
function random() {
    var x = Math.sin(seed++) * 10000;
    return x - Math.floor(x);
}

seed0으로 설정하거나 Math.PI의 배수를 피하면서 임의의 숫자로 설정할 수 있습니다 .

내 생각에이 솔루션의 우아함은 "매직"숫자가 없기 때문에 발생합니다 (10000 이외의 숫자는 홀수 패턴을 피하기 위해 버려야하는 최소 자릿수를 나타냅니다. 값이 10 , 100 , 1000 인 결과 참조). ). 간결함도 좋습니다.

Math.random ()보다 약간 느리지 만 (2 또는 3의 요소로) JavaScript로 작성된 다른 솔루션만큼 빠르다고 생각합니다.


20
이 RNG가 균일하게 분포 된 숫자를 생성한다는 것을 증명할 수있는 방법이 있습니까? 실험적으로는 다음과 같습니다. jsfiddle.net/bhrLT
Nathan Breit

6
초당 6,000,000 개의 작업 이 매우 빠르므로 클릭당 ~ 3,000,000 개 이상을 생성 할 계획은 없습니다. 키딩, 훌륭합니다.
AMK

59
-1, 이것은 균일 한 샘플러가 아닙니다. 0과 1로 편향되어 있습니다 ( jsfiddle.net/bhrLT/17 참조 , 계산하는 데 시간이 걸릴 수 있음). 연속적인 값은 상관 관계가 있습니다. 매 355 개의 값, 더 나아가서는 710 개의 값이 관련됩니다. 좀 더 신중하게 생각하십시오!
spencer nelson

37
문제는 암호로 안전한 난수 생성기를 만드는 것이 아니라 자바 스크립트에서 작동하고 빠른 데모에 유용한 것입니다. 나는 그 목적을 위해 백만 개의 난수를 잘 보여주는 분포를 제공하는 빠르고 간단한 것을 취할 것입니다.
Jason Goemaat

15
조심해. Math.sin ()은 클라이언트와 서버에서 다른 결과를 제공 할 수 있습니다. 나는 Meteor를 사용합니다 (클라이언트 및 서버에서 자바 스크립트를 사용합니다).
Obiwahn

145

평범한 JavaScript로 PRNG ( Pseudorandom Number Generator ) 기능 을 훌륭하고 짧고 빠르게 구현했습니다 . 그들 모두는 파종 될 수 있고 양질의 숫자를 제공합니다.

우선, PRNG를 올바르게 초기화하십시오. 아래의 대부분의 생성기는 기본 제공 시드 생성 절차가 없지만 (단순화를 위해) PRNG 의 초기 상태 로 하나 이상의 32 비트 값을 허용합니다 . 유사한 시드 (예 : 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은 PRNG에서 시드로 사용되는 새로운 "임의"32 비트 해시 값을 생성합니다. 사용 방법은 다음과 같습니다.

// 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 회 반복) 진행시킵니다. 이것은 종종 PRNG의 참조 구현에서 볼 수 있지만 초기 상태 수를 제한합니다.

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 에 상당 후 0-1 (0 포함 1 명 제외) 사이의 부동 소수점 수로 변환된다 -1) Math.random()는 난수를 원한다면 특정 범위의 MDN에 대한이 기사를 읽으십시오 . 원시 비트 만 원하면 최종 분할 연산을 제거하십시오.

주목해야 할 또 다른 사항은 JS의 한계입니다. 숫자는 최대 53 비트 해상도의 전체 정수만 나타낼 수 있습니다. 비트 단위 연산을 사용할 때는 32로 줄어 듭니다. 이로 인해 64 비트 숫자를 사용하는 C 또는 C ++로 작성된 알고리즘을 구현하기가 어렵습니다. 64 비트 코드를 포팅하려면 성능 이 크게 저하 될 수있는 심이 필요합니다 . 따라서 단순성과 효율성을 위해 32 비트 수학을 사용하는 알고리즘 만 고려했습니다. JS와 직접 호환되기 때문입니다.

이제 발전기로 넘어갑니다. (나는 여기 를 참조하여 전체 목록을 유지합니다 )


sfc32 (간이 고속 카운터)

sfc32PractRand 난수 테스트 스위트 (물론 통과)의 일부입니다. 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;
    }
}

멀 베리 32

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가 필요하고 수십억 개의 임의의 숫자가 필요하지 않은 경우이 방법을 권장 합니다 ( 생일 문제 참조 ).

xoshiro128 **

2018 년 5 월 현재, xoshiro128 ** 는 Vigna / Blackman (Choro에서 사용되는 xoroshiro도 작성 함) 의 Xorshift 제품군 의 새로운 멤버입니다 . 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 (젠킨스 스몰 패스트)

이것은 ISAACSpookyHash 를 만든 Bob Jenkins (2007)의 JSF 또는 'smallprng' 입니다. 그것은 통과 아니지만 빠른 SFC로, PractRand 테스트를 꽤 빨리해야합니다.

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 (일명 Lehmer / Park-Miller RNG 또는 MCG)

LCG는 매우 빠르고 간단하지만 임의의 품질이 너무 낮아서 잘못 사용하면 실제로 프로그램에 버그가 발생할 수 있습니다! 그럼에도 불구하고 Math.sin또는 사용을 제안하는 일부 답변보다 훨씬 낫습니다 Math.PI! 그래도 하나의 라이너입니다. :).

var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;

이 구현 을 1988 년과 1993 년Park–Miller가 제안한대로 최소 표준 RNG 라고하며 C ++ 11에서로 구현됩니다 . 상태는 31 비트입니다 (31 비트는 20 억 개의 가능한 상태를 제공하고 32 비트는 두 배를 제공함). 이것은 다른 사람들이 대체하려는 PRNG 유형입니다!minstd_rand

작동하지만 속도가 실제로 필요하고 무작위성에 신경 쓰지 않는 한 사용 하지 않을 것입니다 (어쨌든 무작위는 무엇입니까?). 게임 잼이나 데모 또는 무언가를 위해 중대한. 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 : 1997 년 4 월 30 일, 크기와 격자 구조가 다른 선형 합동 발생기 표.


5
이것은 놀라운 답변입니다. 나는 이것으로 다시 올 것이다.
DavidsKanal

1
Pierre L' ecuyer의 "Linear Congruential Generators ..."에서 인용 한 값이 Javascript의 최대 정수 크기를 초과 할 수 있다고 생각합니다. (2 ^ 32-1) * 741103597 ≈ 3e18의 최대 시드는 JavaScript의 최대 int 크기 ≈ 9e15보다 큽니다. 피에르의 책에서 다음과 같은 값은 기본 한계 내에서 가장 큰 기간을 갖는다 고 생각합니다 seed = (seed * 185852 + 1) % 34359738337.
Lachmanski

1
@Lachmanski는 사실이지만 32 비트 (및 Park-Miller 31 비트)에 의해 제한됩니다. 를 사용 Math.imul하면 32 비트 정수에서 C의 곱셈을 사용할 때와 같이 오버플로 될 수 있습니다. 당신이 제안하는 것은 JS의 정수 공간의 전체 범위를 활용하는 LCG이며, 이것은 확실히 흥미로운 영역입니다. :)
bryc 2016 년

1
대단해! sfc32를 LGPL 프로그램으로 복사 할 수 있습니까?
user334639

4
@ blobber2 당신이 무슨 뜻인지 확실하지 않지만 원래 코드는 여기 (다른 사람과 함께) : github.com/bryc/code/blob/master/jshash/PRNGs.md 입니다. 저장소 내부의 다소 요점 :-)
bryc

39

아니요, 그러나 여기에 Wikipedia 에서 수정 한 Carp ( Multiply-with-carry) 구현 인 간단한 의사 난수 생성기 가 있습니다 (이후 제거됨).

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 : 심각한 구현 결함이 수정되었습니다.


3
누구 든지이 기능을 무작위로 테스트 했습니까?
Justin

3
이것은 꽤 긴 기간을 가진 MWC (Multiply-with-Cry) 랜덤 발생기입니다. 위키피디아 난수 생성기에서 적용
Michael_Scharf

10
seed함수 는 호출 mz_z될 때 변수가 변경 되므로 랜덤 제너레이터를 재설정하지 않습니다 random(). 따라서mz_z = 987654321seed
Michael_Scharf의

임의 색상 생성기 (HSL)와 함께 사용하면 녹색과 청록색 만 생성합니다. 원래 랜덤 생성기는 모든 색상을 생성합니다. 따라서 동일하지 않거나 작동하지 않습니다.
Tomas Kubes

@Michael_Scharf 1) 시드 변경 m_w이 아닙니다 m_z. 2) m_wm_z이전 값을 기준으로 BASED를 변경하므로 결과가 수정됩니다.
ESL

26

Antti Sykäri의 알고리즘은 훌륭하고 짧습니다. 처음에 Math.seed (s)를 호출 할 때 Javascript의 Math.random을 대체하는 변형을 만들었지 만 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());

이것은 당신에게 자바 스크립트가 가지고 있지 않은 또 다른 기능을 제공합니다 : 다수의 독립적 인 랜덤 생성기. 여러 개의 반복 가능한 시뮬레이션을 동시에 실행하려는 경우 특히 중요합니다.


3
설정 대신 함수를 반환하면 Math.random여러 개의 독립 생성기를 가질 수 있습니다.
Jason Goemaat

1
중요한 경우 무작위 분포에 대한 위의 의견을 확인하십시오. stackoverflow.com/questions/521295/…
jocull

이것에 의해 생성 된 랜덤은 어떻게 반복 될 수 있습니까? 매번 새로운 숫자를 제공합니다
SMUsamaShah

당신이 할 때마다 Math.seed(42);당신이 할 경우이 기능을 다시 설정합니다, 그래서 var random = Math.seed(42); random(); random();당신은 얻을 0.70...0.38.... 전화로 재설정하면var random = Math.seed(42);다시 하면 다음에 전화 random()할 때 0.70...다시, 다음에 0.38...다시 올 것입니다.
WOUNDEDStevenJones

1
이것을 사용하지 마십시오. random대신 기본 자바 스크립트 함수를 덮어 쓰는 대신 이름이 지정된 로컬 변수를 사용하십시오 . 덮어 Math.random쓰면 JIST 컴파일러가 모든 코드를 최적화하지 않을 수 있습니다.
Jack Giffin

11

Pierre L' Ecuyer의 작업은 1980 년대 후반과 1990 년대 초로 거슬러 올라갑니다. 다른 것도 있습니다. 전문가가 아닌 경우 (의사) 난수 생성기를 직접 만드는 것은 결과가 통계적으로 임의적이지 않거나 기간이 짧을 가능성이 높기 때문에 매우 위험합니다. Pierre (및 기타)는 구현하기 쉬운 좋은 (의사) 난수 생성기를 구성했습니다. 나는 그의 LFSR 발전기 중 하나를 사용합니다.

https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf

필 트로이


1
큰 답변이지만 javascript와 관련이 없음 :)
Nikolay Fominyh

3
L' Ecuyer 교수의 작업을 구현하기위한 코드는 공개적으로 제공되며 대부분의 프로그래머가 Javascript로 쉽게 번역 할 수 있습니다.
user2383235

6

이전 답변 중 일부를 결합하여 찾고있는 시드 가능한 임의 함수입니다.

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

4
이것은 다른 씨앗으로 시퀀스의 시작 부분에서 매우 유사한 결과를 생성합니다. 예를 들어, Math.seed(0)()반환 0.2322845458984375Math.seed(1)()반환 0.23228873685002327. 씨앗 m_wm_z씨앗에 따라 변화하는 것이 도움이 될 것 같습니다. var m_w = 987654321 + s; var m_z = 123456789 - s;다른 씨앗으로 첫 번째 값을 훌륭하게 분배합니다.
undefined

1
@undefined는 마지막 편집 당시 수정 된 문제이며 MWC 구현의 버그였습니다.
bryc

2020 년 1 월 현재, 지금은 잘 작동합니다. 0을 입력하면 0.7322976540308446이됩니다. 1, 0.16818441334180534, 2 : 0.6040864314418286, 3 : 0.03998844954185188의 종자. 둘 다 감사합니다!
유레카

3

자신의 의사 랜덤 생성기를 작성하는 것은 매우 간단합니다.

Dave Scotese의 제안은 유용하지만 다른 사람들이 지적했듯이 상당히 균일하게 분포되어 있지 않습니다.

그러나 그것은 죄의 정수 인자 때문이 아닙니다. 그것은 단순히 원의 1 차원 투영으로 일어나는 죄의 범위 때문입니다. 원의 각도를 대신 사용하면 균일합니다.

따라서 sin (x) 대신 arg (exp (i * x)) / (2 * PI)를 사용하십시오.

선형 순서가 마음에 들지 않으면 xor와 조금 섞습니다. 실제 요소는 그다지 중요하지 않습니다.

n 의사 난수를 생성하려면 다음 코드를 사용할 수 있습니다.

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

실제 엔트로피가 필요한 경우 의사 난수 시퀀스를 사용할 수 없습니다.


나는 전문가는 아니지만 순차적 인 씨앗은 일정한 패턴을 따릅니다 . 컬러 픽셀은> = 0.5입니다. 반경을 반복해서 반복하는 것 같아요?
bryc


1

Math.random아니요, 그러나 실행 된 라이브러리가이를 해결합니다. 여기에는 상상할 수있는 거의 모든 분포가 있으며 시드 난수 생성을 지원합니다. 예:

ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)

-1

시드 난수를 반환하는 함수를 작성했습니다. Math.sin을 사용하여 긴 난수를 가지며 시드를 사용하여 숫자를 선택합니다.

사용하다 :

seedRandom("k9]:2@", 15)

첫 번째 매개 변수는 문자열 값 인 시드 번호를 반환합니다. 당신의 씨앗. 두 번째 매개 변수는 반환 할 자릿수입니다.

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

1
이것에 의해 생성 된 숫자의 시퀀스는 실제로 임의의 숫자의 시퀀스의 속성과 비슷하지 않습니다. 그것으로 15 개의 숫자를 생성하면 결과 문자열은 거의 모든 키에 대해 거의 항상 7로 시작합니다.
Gabriel

-2

고정 시드에 대한 간단한 접근 방식 :

function fixedrandom(p){
    const seed = 43758.5453123;
    return (Math.abs(Math.sin(p)) * seed)%1;
}

-6

0에서 100 사이의 숫자입니다.

Number.parseInt(Math.floor(Math.random() * 100))

3
문제는 동일한 시드로 시드 Math.random될 때마다 Math.random동일한 연속적인 난수 시리즈를 생성하도록 시드하는 것입니다. 이 질문은 실제 사용 / 시연에 관한 것이 아닙니다 Math.random.
Jack Giffin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.