Triển khai PCG


31

Vấn đề nào tốt hơn cho PCG.SE so với việc triển khai PCG, Trình tạo số ngẫu nhiên tốt hơn ? Bài báo mới này tuyên bố sẽ trình bày một trình tạo số ngẫu nhiên nhanh, khó dự đoán, nhỏ, tối ưu về mặt thống kê.

Việc thực hiện C tối thiểu của nó chỉ là khoảng chín dòng:

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

(từ: http://www.pcg-random.org/doad.html )

Câu hỏi là: bạn có thể làm tốt hơn không?

Quy tắc

Viết chương trình hoặc xác định hàm thực hiện PCG trên các số nguyên không dấu 32 bit. Điều này khá rộng: bạn có thể in ra một chuỗi vô hạn, xác định pcg32_random_rhàm và cấu trúc tương ứng, v.v.

Bạn phải có khả năng khởi tạo trình tạo số ngẫu nhiên tương đương với hàm C sau:

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

(từ pcg_basic.c:37:)

Gọi trình tạo số ngẫu nhiên mà không cần gieo hạt trước là hành vi không xác định.

Để dễ dàng kiểm tra trình của bạn, hãy xác minh rằng, khi được gieo bằng initstate = 42initseq = 52, đầu ra là 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

Chấm điểm

Điểm chuẩn. Đo bằng byte. Thấp nhất là tốt nhất. Trong trường hợp hòa, nộp trước đó thắng. Tiêu chuẩn áp dụng.

Giải pháp mẫu

Biên dịch gcc -W -Wallsạch sẽ (phiên bản 4.8.2).

So sánh trình của bạn với điều này để đảm bảo bạn nhận được cùng một trình tự.

#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;
}

Trình tự:

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

Vì vậy, ngôn ngữ nhiệm vụ của bạn có liên quan?
Knerd

@Knerd Không. C chỉ là một ví dụ.
wchargein

Không thể chờ đợi để xem triển khai javascript nhỏ ..
Daniel Baird

Câu trả lời:


17

CJam, 109 107 104 98 91 byte

Điều này sử dụng một số ký tự bên ngoài phạm vi ASCII, nhưng tất cả chúng đều nằm trong ASCII mở rộng, vì vậy tôi đang đếm mỗi ký tự là một byte (thay vì tính là UTF-8).

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

Đây về cơ bản là một cổng chính xác của mã C.

Hàm seed là một khối được lưu trữ Svà hàm ngẫu nhiên là một khối được lưu trữ trong R. Smong đợi initstateinitseqtrên ngăn xếp và gieo hạt PRNG. Rkhông mong đợi bất cứ điều gì trên ngăn xếp và để lại số ngẫu nhiên tiếp theo trên nó.

Vì gọi Rtrước khi gọi Slà hành vi không xác định, tôi thực sự xác định R bên trong S , vì vậy cho đến khi bạn sử dụng khối hạt giống, Rchỉ là một chuỗi trống và vô dụng.

Các stateđược lưu trữ trong biến Aincđược lưu trữ trong B.

Giải trình:

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

Và đây là tương đương với khai thác thử nghiệm trong OP:

42 52 S
{RN}16*

trong đó in cùng một số chính xác.

Kiểm tra nó ở đây. Stack Exchange loại bỏ hai ký tự không thể in được, vì vậy nó sẽ không hoạt động nếu bạn sao chép đoạn mã trên. Thay vào đó, sao chép mã từ bộ đếm ký tự .


Khẳng định: hoạt động như quảng cáo.
wchargein

11

C, 195

Tôi cho rằng ai đó nên đăng một triển khai C nhỏ gọn hơn, ngay cả khi nó không có cơ hội chiến thắng. Dòng thứ ba chứa hai hàm: r()(tương đương pcg32_random_r()) và s()(tương đương pcg32_srandom_r()). Dòng cuối cùng là main()hàm, được loại trừ khỏi số ký tự.

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

Mặc dù trình biên dịch sẽ phàn nàn, nhưng điều này sẽ hoạt động đúng trên máy 64 bit. Trên một máy 32-bit bạn sẽ phải bổ sung thêm 5 byte để thay đổi #define L longđể #define L long long( như trong bản demo ideone này ).


Xác nhận: hoạt động như quảng cáo cho tôi (GCC 4.8.2 trên Mint 64-bit).
wchargein

Tôi sẽ phải quy định rằng srandomchức năng này là một phần của trình của bạn và nên được bao gồm trong số lượng ký tự. (Rốt cuộc, có lẽ bạn có thể nghĩ ra một số cách thông minh để tối ưu hóa điều này.) Điều này sẽ mang lại số điểm hiện tại của bạn lên tới 197, theo tính của tôi.
wchargein

@WChargin À, được rồi. Tôi đếm 195 byte.
squossish ossifrage

5

Julia, 218 199 191 byte

Đổi tên các loại dữ liệu cộng với một vài thủ thuật nhỏ khác đã giúp tôi cắt giảm độ dài thêm 19 byte. Chủ yếu bằng cách bỏ qua hai bài tập loại :: 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))

Giải thích về các tên (có tên theo phiên bản không được ghi bên dưới):

# 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

Khởi tạo hạt giống với trạng thái 42 và chuỗi 52. Do chương trình nhỏ hơn, bạn cần nêu rõ loại dữ liệu trong quá trình khởi tạo ngay bây giờ (đã lưu 14 byte mã hoặc hơn). Bạn có thể bỏ qua việc gán loại rõ ràng trên các hệ thống 64 bit:

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

Sản xuất bộ số ngẫu nhiên đầu tiên:

b(r)

Kết quả:

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

Tôi thực sự ngạc nhiên, rằng ngay cả phiên bản Julia vô danh của tôi bên dưới cũng nhỏ hơn nhiều (543 byte) so với giải pháp mẫu trong C (958 byte).

Phiên bản đã được chỉnh sửa, 543 byte

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

Bạn khởi tạo hạt giống (trạng thái ban đầu = 42, chuỗi ban đầu = 52) với:

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

Sau đó, bạn có thể tạo số ngẫu nhiên với:

pcg32randomr(rng)

Đây là kết quả của một kịch bản thử nghiệm:

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

Là một lập trình viên đáng kinh ngạc, tôi phải mất gần một ngày để nó hoạt động. Lần cuối cùng tôi thử mã hóa thứ gì đó trong C (thực ra là C ++) là gần 18 năm trước, nhưng cuối cùng rất nhiều google-fu đã giúp tôi tạo ra một phiên bản Julia hoạt động. Tôi phải nói rằng, tôi đã học được rất nhiều chỉ trong vài ngày trên codegolf. Bây giờ tôi có thể bắt đầu làm việc trên một phiên bản Piet. Đó sẽ là rất nhiều công việc, nhưng tôi thực sự, thực sự muốn có một trình tạo số ngẫu nhiên (tốt) cho Piet;)

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.