PCG 구현


31

나은 난수 생성기 인 PCG 를 구현하는 것보다 PCG.SE의 더 나은 문제는 무엇입니까 ? 이 새로운 논문 은 빠르고 예측하기 어려운 작고 통계적으로 최적 인 난수 생성기를 제시한다고 주장합니다.

최소 C 구현은 약 9 줄입니다.

// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)

typedef struct { uint64_t state;  uint64_t inc; } pcg32_random_t;

uint32_t pcg32_random_r(pcg32_random_t* rng)
{
    uint64_t oldstate = rng->state;
    // Advance internal state
    rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1);
    // Calculate output function (XSH RR), uses old state for max ILP
    uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
    uint32_t rot = oldstate >> 59u;
    return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}

( 출처 : http://www.pcg-random.org/download.html )

문제는 : 더 잘할 수 있습니까?

규칙

32 비트 부호없는 정수에서 PCG를 구현하는 프로그램을 작성하거나 함수를 정의하십시오. 이것은 매우 광범위합니다. 무한 시퀀스를 출력하고 pcg32_random_r함수와 해당 구조체를 정의 할 수 있습니다 .

다음 C 함수와 동일하게 난수 생성기를 시드 할 수 있어야합니다.

// pcg32_srandom(initstate, initseq)
// pcg32_srandom_r(rng, initstate, initseq):
//     Seed the rng.  Specified in two parts, state initializer and a
//     sequence selection constant (a.k.a. stream id)

void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
{
    rng->state = 0U;
    rng->inc = (initseq << 1u) | 1u;
    pcg32_random_r(rng);
    rng->state += initstate;
    pcg32_random_r(rng);
}

(에서 : pcg_basic.c:37)

먼저 시드하지 않고 난수 생성기를 호출하는 것은 정의되지 않은 동작입니다.

제출 내용을 쉽게 확인하려면 initstate = 42and로 시드 할 때 initseq = 52출력이 2380307335다음 과 같은지 확인하십시오 .

$ tail -n 8 pcg.c 
int main()
{
    pcg32_random_t pcg;
    pcg32_srandom_r(&pcg, 42u, 52u);

    printf("%u\n", pcg32_random_r(&pcg));
    return 0;
}
$ gcc pcg.c
$ ./a.out 
2380307335

채점

표준 점수. 바이트 단위로 측정됩니다. 최저가 최고입니다. 동점 인 경우, 조기 제출이 이깁니다. 표준 허점이 적용됩니다.

샘플 솔루션

gcc -W -Wall깨끗하게 컴파일합니다 (버전 4.8.2).

제출 내용을 이것과 비교하여 동일한 순서를 얻으십시오.

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

typedef struct { uint64_t state;  uint64_t inc; } pcg32_random_t;

uint32_t pcg32_random_r(pcg32_random_t* rng)
{
    uint64_t oldstate = rng->state;
    // Advance internal state
    rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1);
    // Calculate output function (XSH RR), uses old state for max ILP
    uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
    uint32_t rot = oldstate >> 59u;
    return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}

void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
{
    rng->state = 0U;
    rng->inc = (initseq << 1u) | 1u;
    pcg32_random_r(rng);
    rng->state += initstate;
    pcg32_random_r(rng);
}

int main()
{
    size_t i;
    pcg32_random_t pcg;
    pcg32_srandom_r(&pcg, 42u, 52u);

    for (i = 0; i < 16; i++)
    {
        printf("%u\n", pcg32_random_r(&pcg));
    }
    return 0;
}

순서:

2380307335
948027835
187788573
3952545547
2315139320
3279422313
2401519167
2248674523
3148099331
3383824018
2720691756
2668542805
2457340157
3945009091
1614694131
4292140870

작업 언어와 관련이 있습니까?
Knerd

@Knerd Nope. C는 단지 예일뿐입니다.
wchargin

작은 자바 스크립트 구현을 기다릴 수 없다.
Daniel Baird

답변:


17

CJam, 109 (107) 104 98 91 바이트

이것은 ASCII 범위 밖의 일부 문자를 사용하지만 모두 확장 ASCII 내에 있습니다. 따라서 UTF-8로 계산하는 대신 각 문자를 바이트로 계산합니다.

{[2*0:A)|:B{AA"XQô-L-"256b*B1|+GG#%:A;__Im>^27m>_@59m>_@\m>@@~)31&m<|4G#%}:R~@A+:AR];}:S;

이것은 기본적으로 C 코드의 정확한 포트입니다.

seed 함수는에 저장된 블록 S이고 random 함수는에 저장된 블록입니다 R. 스택에서 PRNG를 S예상 initstate하고 initseq시드합니다. R스택에 아무것도 기대하지 않고 다음 임의의 숫자를 남겨 둡니다.

호출 이후 R호출하기 전에하는 것은 S정의되지 않은 동작은, 실제로 정의하고있어 R S 당신이 씨 블록을 사용할 때까지 그래서, R그냥 빈 문자열과 쓸모가 없다.

state변수에 저장 A하고,이 inc저장된다 B.

설명:

"The seed block S:";
[       "Remember start of an array. This is to clear the stack at the end.";
2*      "Multiply initseq by two, which is like a left-shift by one bit.";
0:A     "Store a 0 in A.";
)|:B    "Increment to get 1, bitwise or, store in B.";
{...}:R "Store this block in R. This is the random function.";
~       "Evaluate the block.";
@A+:A   "Pull up initstate, add to A and store in A.";
R       "Evaluate R again.";
];      "Wrap everything since [ in an array and discard it.";

"The random block R:";
AA            "Push two A's, one of them to remember oldstate.";
"XQô-L-"256b* "Push that string and interpret the character codes as base-256 digits.
               Then multiply A by this.";
B1|+          "Take bitwise or of 1 and inc, add to previous result.";
GG#%:A;       "Take modulo 16^16 (== 2^64). Store in A. Pop.";
__            "Make two copies of old state.";
Im>^          "Right-shift by 18, bitwise xor.";
27m>_         "Right-shift by 27. Duplicate.";
@59m>         "Pull up remaining oldstate. Right-shift by 59.";
_@\m>         "Duplicate, pull up xorshifted, swap order, right-shift.";
@@            "Pull up other pair of xorshifted and rot.";
~)            "Bitwise negation, increment. This is is like multiplying by -1.";
31&           "Bitwise and with 31. This is the reason I can actually use a negative value
               in the previous step.";
m<|           "Left-shift, bitwise or.";
4G#%          "Take modulo 4^16 (== 2^32).";

다음은 OP의 테스트 장치와 동일합니다.

42 52 S
{RN}16*

정확히 같은 숫자를 인쇄합니다.

여기에서 테스트하십시오. Stack Exchange는 인쇄 할 수없는 두 문자를 제거하므로 위의 스 니펫을 복사하면 작동하지 않습니다. 대신 문자 카운터 에서 코드를 복사하십시오 .


확인 됨 : 광고 된대로 작동합니다.
wchargin

11

C, 195

나는 이길 가능성이 없더라도 누군가가 더 컴팩트 한 C 구현을 게시해야한다고 생각했다. 세 번째 줄에는 r()(와 등가 pcg32_random_r()) 및 s()(등가 )의 두 가지 기능이 있습니다 pcg32_srandom_r(). 마지막 줄은 main()문자 수에서 제외되는 함수입니다.

#define U unsigned
#define L long
U r(U L*g){U L o=*g;*g=o*0x5851F42D4C957F2D+(g[1]|1);U x=(o>>18^o)>>27;U t=o>>59;return x>>t|x<<(-t&31);}s(U L*g,U L i,U L q){*g++=0;*g--=q+q+1;r(g);*g+=i;r(g);}
main(){U i=16;U L g[2];s(g,42,52);for(;i;i--)printf("%u\n",r(g));}

컴파일러가 불평하지만 64 비트 시스템에서 제대로 작동합니다. 32 비트 컴퓨터에서 당신은 변화에 다른 5 바이트를 추가해야합니다 #define L long#define L long long( 이 ideone 데모처럼 ).


확인 : 나에게 광고 된대로 작동합니다 (Mint 64 비트의 GCC 4.8.2).
wchargin

나는 것을 지배 할 것이다 srandom기능이 제출의 일부이며 글자 수에 포함되어야한다. (어쩌면 이것을 최적화하는 영리한 방법을 생각할 수도 있습니다.) 이것은 현재 점수를 197까지 올릴 것입니다.
wchargin

@WChargin 아, 그럼. 나는 195 바이트를 세었다.
squeamish ossifrage

5

줄리아, 218 199 191 바이트

데이터 유형의 이름을 바꾸고 몇 가지 다른 트릭을 사용하면 길이를 추가로 19 바이트 줄였습니다. 주로 두 개의 :: Int64 유형 할당 을 생략하여 .

type R{T} s::T;c::T end
R(s,c)=new(s,c);u=uint32
a(t,q)=(r.s=0x0;r.c=2q|1;b(r);r.s+=t;b(r))
b(r)=(o=uint64(r.s);r.s=o*0x5851f42d4c957f2d+r.c;x=u((o>>>18$o)>>>27);p=u(o>>>59);x>>>p|(x<<-p&31))

이름에 대한 설명 (아래에 ungolfed 버전의 이름이 있음) :

# R     : function Rng
# a     : function pcg32srandomr
# b     : function pcg32randomr
# type R: type Rng
# r.s   : rng.state
# r.c   : rng.inc
# o     : oldstate
# x     : xorshifted
# t     : initstate
# q     : initseq
# p     : rot
# r     : rng
# u     : uint32

상태 42와 시퀀스 52를 사용하여 시드를 초기화하십시오. 더 작은 프로그램으로 인해 초기화 중에 데이터 유형을 명시 적으로 명시해야합니다 (14 바이트의 코드 저장 등). 64 비트 시스템에서 명시 적 유형 지정을 생략 할 수 있습니다.

r=R(42,52) #on 64-bit systems or r=R(42::Int64,52::Int64) on 32 bit systems
a(r.s,r.c)

첫 번째 난수 세트를 생성하십시오.

b(r)

결과:

julia> include("pcg32golfed.jl")
Checking sequence...
result round 1: 2380307335
target round 1: 2380307335   pass
result round 2: 948027835
target round 2: 948027835   pass
result round 3: 187788573
target round 3: 187788573   pass
             .
             .
             .

아래의 ungolfed Julia 버전조차도 C (958 바이트)의 샘플 솔루션보다 훨씬 작습니다 (543 바이트).

Ungolfed 버전, 543 바이트

type Rng{T}
    state::T
    inc::T
end

function Rng(state,inc)
    new(state,inc)
end

function pcg32srandomr(initstate::Int64,initseq::Int64)
    rng.state =uint32(0)
    rng.inc   =(initseq<<1|1)
    pcg32randomr(rng)
    rng.state+=initstate
    pcg32randomr(rng)
end

function pcg32randomr(rng)
    oldstate  =uint64(rng.state)
    rng.state =oldstate*uint64(6364136223846793005)+rng.inc
    xorshifted=uint32(((oldstate>>>18)$oldstate)>>>27)
    rot       =uint32(oldstate>>>59)
    (xorshifted>>>rot) | (xorshifted<<((-rot)&31))
end

다음을 사용하여 시드 (초기 상태 = 42, 초기 시퀀스 = 52)를 초기화합니다.

rng=Rng(42,52)
pcg32srandomr(rng.state,rng.inc)

그런 다음 다음을 사용하여 난수를 만들 수 있습니다.

pcg32randomr(rng)

테스트 스크립트의 결과는 다음과 같습니다.

julia> include("pcg32test.jl")
Test PCG
Initialize seed...
Checking sequence...
result round 1: 2380307335
target round 1: 2380307335   pass
result round 2: 948027835
target round 2: 948027835   pass
result round 3: 187788573
target round 3: 187788573   pass
             .
             .
             .
result round 14: 3945009091
target round 14: 3945009091   pass
result round 15: 1614694131
target round 15: 1614694131   pass
result round 16: 4292140870
target round 16: 4292140870   pass

내가 끔찍한 프로그래머이기 때문에 거의 하루가 걸렸습니다. 마지막으로 C (실제로 C ++)로 코딩하려고 시도한 것은 거의 18 년 전이었습니다. 그러나 많은 google-fu가 마침내 Julia 버전을 만드는 데 도움이되었습니다. 나는 며칠 동안 codegolf에서 많은 것을 배웠습니다. 이제 Piet 버전 작업을 시작할 수 있습니다. 그것은 많은 일이 될 것이지만 실제로는 정말 Piet에 대한 (좋은) 난수 생성기를 원합니다.)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.