Mettre en œuvre un PCG


31

Quel meilleur problème pour PCG.SE que d'implémenter PCG, un meilleur générateur de nombres aléatoires ? Ce nouvel article prétend présenter un générateur de nombres aléatoires rapide, difficile à prévoir, petit et statistiquement optimal.

Son implémentation minimale en C est d'environ neuf lignes:

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

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

La question est: pouvez-vous faire mieux?

Règles

Écrivez un programme ou définissez une fonction qui implémente PCG sur des entiers non signés 32 bits. C'est assez large: vous pouvez imprimer une séquence infinie, définir une pcg32_random_rfonction et une structure correspondante, etc.

Vous devez être en mesure d'amorcer votre générateur de nombres aléatoires de manière équivalente à la fonction C suivante:

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

( à partir de : pcg_basic.c:37)

Appeler le générateur de nombres aléatoires sans l'amorcer au préalable est un comportement indéfini.

Pour vérifier facilement votre soumission, assurez-vous que, une fois amorcé avec initstate = 42et initseq = 52, le résultat est 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

Notation

Score standard. Mesuré en octets. Le plus bas est le meilleur. En cas d'égalité, la soumission antérieure l'emporte. Les failles standard s'appliquent.

Exemple de solution

Compile sous gcc -W -Wallproprement (version 4.8.2).

Comparez votre soumission à celle-ci pour vous assurer d'obtenir la même séquence.

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

Séquence:

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

Votre langage de tâche est-il donc lié?
Knerd

@Knerd Nope. Le C n'est qu'un exemple.
wchargin

J'ai hâte de voir une petite implémentation de javascript ..
Daniel Baird

Réponses:


17

CJAM, 109 107 104 98 91 octets

Cela utilise certains caractères en dehors de la plage ASCII, mais ils sont tous en ASCII étendu, donc je compte chaque caractère comme un octet (au lieu de compter comme UTF-8).

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

Il s'agit essentiellement d'un port exact du code C.

La fonction de départ est un bloc stocké dans S, et la fonction aléatoire est un bloc stocké dans R. Sattend initstateet initseqsur la pile et ensemencer le PRNG. Rn'attend rien sur la pile et laisse le prochain nombre aléatoire dessus.

Étant donné qu'appeler Ravant d'appeler Sest un comportement indéfini, je définis en fait à l' R intérieur S , donc jusqu'à ce que vous utilisiez le bloc de départ, Rc'est juste une chaîne vide et inutile.

Le stateest stocké dans une variable Aet le incest stocké dans B.

Explication:

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

Et voici l'équivalent du harnais de test dans l'OP:

42 52 S
{RN}16*

qui imprime exactement les mêmes nombres.

Testez-le ici. Stack Exchange supprime les deux caractères non imprimables, donc cela ne fonctionnera pas si vous copiez l'extrait ci-dessus. Copiez plutôt le code du compteur de caractères .


Confirmé: fonctionne comme annoncé.
wchargin

11

C, 195

J'ai pensé que quelqu'un devrait publier une implémentation C plus compacte, même si elle n'a aucune chance de gagner. La troisième ligne contient deux fonctions: r()(équivalent à pcg32_random_r()) et s()(équivalent à pcg32_srandom_r()). La dernière ligne est la main()fonction, qui est exclue du nombre de caractères.

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

Bien que le compilateur se plaindra, cela devrait fonctionner correctement sur une machine 64 bits. Sur une machine 32 bits , vous devrez ajouter 5 autres octets au changement #define L longà #define L long long( comme dans cette démo ideone ).


Confirmé: fonctionne comme annoncé pour moi (GCC 4.8.2 sur Mint 64 bits).
wchargin

Je devrais décider que la srandomfonction fait partie de votre soumission et devrait être incluse dans le nombre de caractères. (Après tout, vous pourriez peut-être penser à un moyen intelligent d'optimiser cela.) Cela porterait votre score actuel à 197, d'après mon compte.
wchargin

@WChargin Ah, OK alors. J'ai compté 195 octets.
squeamish ossifrage

5

Julia, 218 199 191 octets

Renommer les types de données et quelques autres petites astuces m'a aidé à réduire la longueur de 19 octets supplémentaires. Principalement en omettant deux affectations de type :: 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))

Explication des noms (avec les noms correspondants dans la version non golfée ci-dessous):

# 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

Initialisez la graine avec l'état 42 et la séquence 52. En raison du programme plus petit, vous devez indiquer le type de données de manière explicite lors de l'initialisation maintenant (14 octets de code enregistrés environ). Vous pouvez omettre l'attribution de type explicite sur les systèmes 64 bits:

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

Produisez le premier ensemble de nombres aléatoires:

b(r)

Résultat:

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

J'ai été vraiment surpris que même ma version Julia non golfée ci-dessous soit tellement plus petite (543 octets) que l'exemple de solution en C (958 octets).

Version non golfée, 543 octets

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

Vous initialisez la graine (état initial = 42, séquence initiale = 52) avec:

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

Ensuite, vous pouvez créer des nombres aléatoires avec:

pcg32randomr(rng)

Voici le résultat d'un script de 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

Comme je suis un mauvais programmeur, il m'a fallu presque un jour pour le faire fonctionner. La dernière fois que j'ai essayé de coder quelque chose en C (en fait C ++), c'était il y a presque 18 ans, mais beaucoup de google-fu m'ont finalement aidé à créer une version Julia fonctionnelle. Je dois dire que j'ai beaucoup appris en quelques jours sur codegolf. Maintenant, je peux commencer à travailler sur une version Piet. Ça va être beaucoup de travail, mais je veux vraiment, vraiment un (bon) générateur de nombres aléatoires pour Piet;)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.