racket/c/random.c
Matthew Flatt 18d18b7ff6 add pseudo-random generator API
The MRG32k3a generator is fast when using unboxed floating-point
arithemtic. Since the Scheme compiler doesn't yet support that,
build MRG32k3a into the kernel and provide access via
`pseudo-random-generator` functions.

original commit: 3dd74679a6c2705440488d8c07c47852eb50a94b
2019-10-07 10:58:39 -06:00

189 lines
5.0 KiB
C

#include "system.h"
/*
Based on
Implementation of SRFI-27 core generator in C for Racket.
dvanhorn@cs.uvm.edu
and
54-BIT (double) IMPLEMENTATION IN C OF THE "MRG32K3A" GENERATOR
===============================================================
Sebastian.Egner@philips.com, Mar-2002, in ANSI-C and Scheme 48 0.57
This code is a C-implementation of Pierre L'Ecuyer's MRG32k3a generator.
The code uses (double)-arithmetics, assuming that it covers the range
{-2^53..2^53-1} exactly (!). The code of the generator is based on the
L'Ecuyer's own implementation of the generator. Please refer to the
file 'mrg32k3a.scm' for more information about the method.
*/
/* Representation is arecord with 6 `double` fields: */
#define RANDSTATEX10(x) (((double*)&RECORDINSTIT(x, 0))[0])
#define RANDSTATEX11(x) (((double*)&RECORDINSTIT(x, 0))[1])
#define RANDSTATEX12(x) (((double*)&RECORDINSTIT(x, 0))[2])
#define RANDSTATEX20(x) (((double*)&RECORDINSTIT(x, 0))[3])
#define RANDSTATEX21(x) (((double*)&RECORDINSTIT(x, 0))[4])
#define RANDSTATEX22(x) (((double*)&RECORDINSTIT(x, 0))[5])
/* The Generator
=============
*/
/* moduli of the components */
#define Im1 0xffffff2f
#define Im2 0xffffa6bb
#define m1 4294967087.0
#define m2 4294944443.0
/* recursion coefficients of the components */
#define a12 1403580.0
#define a13n 810728.0
#define a21 527612.0
#define a23n 1370589.0
/* normalization factor 1/(m1 + 1) */
#define norm 2.328306549295728e-10
/* the actual generator */
static double mrg32k3a(ptr s) { /* (double), in {0..m1-1} */
double x10, x20, y;
iptr k10, k20;
/* component 1 */
x10 = a12*(RANDSTATEX11(s)) - a13n*(RANDSTATEX12(s));
k10 = (iptr)(x10 / m1);
x10 -= k10 * m1;
if (x10 < 0.0)
x10 += m1;
RANDSTATEX12(s) = RANDSTATEX11(s);
RANDSTATEX11(s) = RANDSTATEX10(s);
RANDSTATEX10(s) = x10;
/* component 2 */
x20 = a21*(RANDSTATEX20(s)) - a23n*(RANDSTATEX22(s));
k20 = (iptr)(x20 / m2);
x20 -= k20 * m2;
if (x20 < 0.0)
x20 += m2;
RANDSTATEX22(s) = RANDSTATEX21(s);
RANDSTATEX21(s) = RANDSTATEX20(s);
RANDSTATEX20(s) = x20;
/* combination of component */
y = x10 - x20;
if (y < 0.0)
y += m1;
return y;
}
/**************************************************/
/* The number `n` must be no more than 4294967087 */
uptr S_random_state_next_integer(ptr s, uptr n)
{
double x, q, qn, xq;
/* generate result in {0..n-1} using the rejection method */
q = (double)( (uptr)(m1 / (double)n) );
qn = q * n;
do {
x = mrg32k3a(s);
} while (x >= qn);
xq = x / q;
/* return result */
return (uptr)xq;
}
double S_random_state_next_double(ptr s)
{
double x;
x = mrg32k3a(s);
return (x + 1.0) * norm;
}
/**************************************************/
static UINT _random_m(UINT *_x)
{
UINT x, y;
x = *_x;
y = x & 0xFFFF;
x = (30903 * y) + (x >> 16);
*_x = x;
return y;
}
static int _random_n(UINT *_x, int n)
{
return ((_random_m(_x) << 16) + _random_m(_x)) % n;
}
static void sch_srand_half(UINT x, ptr s)
{
/* Due to integer overflow, this doesn't match the Scheme implementation!
We use "int" instead of "long" to make the overflow consistent
across platforms (since "long" is sometimes 64 bits). */
UINT z;
z = _random_n(&x, Im1-1);
RANDSTATEX10(s) = (double)(1 + (((UINT)RANDSTATEX10(s) + z) % (Im1 - 1)));
z = _random_n(&x, Im1);
RANDSTATEX11(s) = (double)(((UINT)RANDSTATEX11(s) + z) % Im1);
z = _random_n(&x, Im1);
RANDSTATEX12(s) = (double)(((UINT)RANDSTATEX12(s) + z) % Im1);
z = _random_n(&x, Im2-1);
RANDSTATEX20(s) = (double)(1 + (((UINT)RANDSTATEX20(s) + z) % (Im2 - 1)));
z = _random_n(&x, Im2);
RANDSTATEX21(s) = (double)(((UINT)RANDSTATEX21(s) + z) % Im2);
z = _random_n(&x, Im2);
RANDSTATEX22(s) = (double)(((UINT)RANDSTATEX22(s) + z) % Im2);
/* Due to the mismatch, maybe it's possible that we can hit a degeneracy?
Double-check, just in case... */
if (!RANDSTATEX10(s) && !RANDSTATEX11(s) && !RANDSTATEX12(s))
RANDSTATEX10(s) = 1;
if (!RANDSTATEX20(s) && !RANDSTATEX21(s) && !RANDSTATEX22(s))
RANDSTATEX20(s) = 1;
}
void S_random_state_init(ptr s, UINT x)
{
/* Initial values are from Sebastian Egner's implementation: */
RANDSTATEX10(s) = 1062452522.0;
RANDSTATEX11(s) = 2961816100.0;
RANDSTATEX12(s) = 342112271.0;
RANDSTATEX20(s) = 2854655037.0;
RANDSTATEX21(s) = 3321940838.0;
RANDSTATEX22(s) = 3542344109.0;
sch_srand_half(x & 0xFFFF, s);
sch_srand_half((x >> 16) & 0xFFFF, s);
}
/**************************************************/
IBOOL S_random_state_check(double x10, double x11, double x12,
double x20, double x21, double x22)
{
#define CHECK_RS_FIELD(x, top) if ((x < 0.0) || (x > ((top) - 1))) return 0;
CHECK_RS_FIELD(x10, Im1)
CHECK_RS_FIELD(x11, Im1)
CHECK_RS_FIELD(x12, Im1)
CHECK_RS_FIELD(x20, Im2)
CHECK_RS_FIELD(x21, Im2)
CHECK_RS_FIELD(x22, Im2)
if ((x10 == 0.0) && (x11 == 0.0) && (x12 == 0.0))
return 0;
if ((x20 == 0.0) && (x21 == 0.0) && (x22 == 0.0))
return 0;
return 1;
}