Implementare un PCG


31

Quale problema migliore per PCG.SE che implementare PCG, un migliore generatore di numeri casuali ? Questo nuovo documento afferma di presentare un generatore di numeri casuali veloce, difficile da prevedere, piccolo e statisticamente ottimale.

La sua implementazione C minima è di circa nove righe:

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

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

La domanda è: puoi fare di meglio?

Regole

Scrivi un programma o definisci una funzione che implementa PCG su numeri interi senza segno a 32 bit. Questo è abbastanza ampio: potresti stampare una sequenza infinita, definire una pcg32_random_rfunzione e una struttura corrispondente, ecc.

Devi essere in grado di eseguire il seeding del generatore di numeri casuali in modo equivalente alla seguente funzione 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);
}

(da pcg_basic.c:37:)

Chiamare il generatore di numeri casuali senza prima eseguirne il seeding è un comportamento indefinito.

Per controllare facilmente l'invio, verificare che, quando viene eseguito il seeding con initstate = 42e initseq = 52, l'output è 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

punteggio

Punteggio standard. Misurato in byte. Il più basso è il migliore. In caso di pareggio, vince l'invio precedente. Si applicano scappatoie standard .

Soluzione di esempio

Compilare in gcc -W -Wallmodo pulito (versione 4.8.2).

Confronta il tuo invio con questo per assicurarti di ottenere la stessa sequenza.

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

Sequenza:

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

Quindi il tuo compito è legato alla lingua?
Knerd,

@Knerd Nope. La C è solo un esempio.
wchargin,

Non vedo l'ora di vedere una piccola implementazione javascript ..
Daniel Baird,

Risposte:


17

CJam, 109 107 104 98 91 byte

Questo utilizza alcuni caratteri al di fuori dell'intervallo ASCII, ma sono tutti all'interno di ASCII esteso, quindi sto contando ogni carattere come byte (invece di contare come UTF-8).

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

Questa è sostanzialmente una porta esatta del codice C.

La funzione seed è un blocco memorizzato in S, e la funzione random è un blocco memorizzato in R. Ssi aspetta initstatee initseqin pila e semina il PRNG. Rnon si aspetta nulla dallo stack e lascia il prossimo numero casuale su di esso.

Poiché chiamare Rprima di chiamare Sè un comportamento indefinito, in realtà sto definendo R all'interno S , quindi fino a quando non si utilizza il blocco seme, Rè solo una stringa vuota e inutile.

Il stateè memorizzato in variabile Ae il incè memorizzato in B.

Spiegazione:

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

Ed ecco l'equivalente del cablaggio di prova nell'OP:

42 52 S
{RN}16*

che stampa gli stessi stessi numeri.

Provalo qui. Stack Exchange rimuove i due caratteri non stampabili, quindi non funzionerà se copi lo snippet di cui sopra. Copia invece il codice dal contatore dei caratteri .


Confermato: funziona come pubblicizzato.
wchargin,

11

C, 195

Ho pensato che qualcuno dovrebbe pubblicare un'implementazione C più compatta, anche se non ha possibilità di vincere. La terza riga contiene due funzioni: r()(equivalente a pcg32_random_r()) e s()(equivalente a pcg32_srandom_r()). L'ultima riga è la main()funzione, che è esclusa dal conteggio dei caratteri.

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

Anche se il compilatore si lamenterà, questo dovrebbe funzionare correttamente su una macchina a 64 bit. Su una macchina a 32 bit dovrete aggiungere un altro 5 byte al cambiamento #define L longper #define L long long( come in questo Ideone demo ).


Confermato: funziona come pubblicizzato per me (GCC 4.8.2 su Mint 64-bit).
wchargin,

Dovrei decidere che la srandomfunzione fa parte della tua proposta e dovrebbe essere inclusa nel conteggio dei personaggi. (Dopotutto, forse potresti pensare a un modo intelligente per ottimizzare questo.) Ciò porterebbe il tuo punteggio attuale a 197, secondo il mio conteggio.
wchargin,

@WChargin Ah, OK allora. Ho contato 195 byte.
ossifrage schizzinoso,

5

Julia, 218 199 191 byte

La ridenominazione dei tipi di dati oltre ad alcuni altri piccoli trucchi mi ha aiutato a ridurre la lunghezza di ulteriori 19 byte. Principalmente omettendo due assegnazioni di tipo :: 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))

Spiegazione dei nomi (con i nomi corrispondenti nella versione non golfata di seguito):

# 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

Inizializza il seme con lo stato 42 e la sequenza 52. A causa del programma più piccolo, devi ora dichiarare esplicitamente il tipo di dati durante l'inizializzazione (circa 14 byte di codice salvati). È possibile omettere l'assegnazione esplicita del tipo su sistemi a 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)

Produci la prima serie di numeri casuali:

b(r)

Risultato:

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

Sono rimasto davvero sorpreso dal fatto che anche la mia versione non modificata di Julia di seguito sia molto più piccola (543 byte) rispetto alla soluzione di esempio in C (958 byte).

Versione non golfata, 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

Si inizializza il seme (stato iniziale = 42, sequenza iniziale = 52) con:

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

Quindi puoi creare numeri casuali con:

pcg32randomr(rng)

Ecco il risultato di uno script di test:

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

Essendo un programmatore abissale, mi ci è voluto quasi un giorno per farlo funzionare. L'ultima volta che ho provato a scrivere qualcosa in C (in realtà C ++) è stato quasi 18 anni fa, ma molto google-fu mi ha finalmente aiutato a creare una versione funzionante di Julia. Devo dire che ho imparato molto in pochi giorni su Codegolf. Ora posso iniziare a lavorare su una versione di Piet. Ci vorrà molto lavoro, ma voglio davvero un (buon) generatore di numeri casuali per Piet;)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.