prns 1.0.0

A Rust implementation of the PRNS fast random-access pseudo-random number generator
Documentation
// Marc B. Reynolds, 2013-2025
// Public Domain under http://unlicense.org, see link for details.
//
// Documentation: http://marc-b-reynolds.github.io/shf/2016/04/19/prns.html

// Short description:
// *

#ifndef PRNS_H
#define PRNS_H

// macro configurations to define (if desired) prior to including
// this header:
//
// PRNS_SMALLCRUSH: weaker (and cheaper) mixing function which is
// sufficient to pass SmallCrush. If undefined the generator will
// pass Crush.
//
// PRNS_MIX: if defined overrides the mixing functions defined
// in this file.
//
// PRNS_WEYL:
// PRNS_WEYL_D:


typedef struct { uint64_t i;  } prns_t;
typedef struct { uint64_t i,k;} prns_down_t;


//***************************************************************
//*** mixing function portion (start)

// only needed if no user defined version provided
#if !defined(PRNS_MIX)

// choose between mixing functions
#ifndef PRNS_MIX_VERSION
#define PRNS_MIX_VERSION 1
#endif

#define PRNS_MIX(X) prns_mix(X)

#if (PRNS_MIX_VERSION == 1)

#if !defined(PRNS_SMALLCRUSH)

#ifndef PRNS_MIX_S0
#ifdef  PRNS_MIX_13
#define PRNS_MIX_S0 30
#define PRNS_MIX_S1 27
#define PRNS_MIX_S2 31
#define PRNS_MIX_M0 UINT64_C(0xbf58476d1ce4e5b9)
#define PRNS_MIX_M1 UINT64_C(0x94d049bb133111eb)
#else
#define PRNS_MIX_S0 31
#define PRNS_MIX_S1 27
#define PRNS_MIX_S2 33
#define PRNS_MIX_M0 UINT64_C(0x7fb5d329728ea185)
#define PRNS_MIX_M1 UINT64_C(0x81dadef4bc2dd44d)
#endif
#endif

static inline uint64_t prns_mix(uint64_t x)
{
  x ^= (x >> PRNS_MIX_S0);
  x *= PRNS_MIX_M0;
  x ^= (x >> PRNS_MIX_S1);
  x *= PRNS_MIX_M1;

#ifndef PRNS_NO_FINAL_XORSHIFT
  x ^= (x >> PRNS_MIX_S2);
#endif

  return x;
}

#else

static inline uint64_t prns_mix(uint64_t x)
{
  x ^= (x >> 33);
  x *= UINT64_C(0xbf58476d1ce4e5b9);

  return x;
}

#endif

#elif (PRNS_MIX_VERSION == 2)

#if defined(PRNS_MIX_DEFAULT)||defined(PRNS_MIX_D_DEFAULT)
#if defined(__ARM_ARCH)
#if defined(__ARM_FEATURE_CRC32)
static inline uint64_t prns_crc32c_64(uint64_t x, uint32_t k) { return __crc32cd(k,x); }
#endif
#endif
#else
static inline uint64_t prns_crc32c_64(uint64_t x, uint32_t k) { return _mm_crc32_u64(k,x); }
#endif

// all wip: need to recheck some math (sigh)

static inline uint64_t prns_mix(uint64_t x)
{
  uint64_t r = x;

  r = prns_crc32c_64(r,0x9e3d2c1b) ^ (r ^ (r >> 17));
  r ^= (r*r) & UINT64_C(-2);
  r = prns_crc32c_64(r,0x9e3d2c1b) ^ (r ^ (r >> 23));
  r ^= (r*r) & UINT64_C(-2);

  return r;
}


static inline uint64_t prns_down_mix(uint64_t x, uint64_t k)
{
  uint64_t r = x ^ k;

  r = prns_crc32c_64(r,0x9e3d2c1b) ^ (r);
  r ^= (r*r) & UINT64_C(-2);
  r = prns_crc32c_64(r,0x9e3d2c1b) ^ (r);
  r ^= (r*r) & UINT64_C(-2);

  return r;
}

#endif



#ifndef PRNS_MIX
#define PRNS_MIX_DEFAULT
#ifndef PRNS_SMALLCRUSH
#define PRNS_MIX(X) prns_mix(X)
#else
#define PRNS_MIX(X) prns_min_mix(X)
#endif
#endif


#endif


#if !defined(PRNS_MIX_D)
#define PRNS_MIX_D(X,K) prns_mix((X)^(K))
#endif


//*** mixing function portion (end)
//***************************************************************

// Weyl constant choices
#ifndef PRNS_WEYL
#define PRNS_WEYL   UINT64_C(0x61c8864680b583eb)
#define PRNS_WEYL_I UINT64_C(0x0e217c1e66c88cc3)
#endif

#ifndef PRNS_WEYL_D
#define PRNS_WEYL_D   UINT64_C(0x4f1bbcdcbfa54001)
#define PRNS_WEYL_D_I UINT64_C(0x4f1bbcdcbfa54001) // NO! compute it and place
#endif


// return the position in the stream
static inline uint64_t prns_tell(prns_t* gen)
{
  return gen->i * PRNS_WEYL_I;
}

// sets the position in the stream
static inline void prns_set(prns_t* gen, uint64_t pos)
{
  gen->i = PRNS_WEYL*pos;
}

// moves the stream position by 'offset'
static inline void prns_seek(prns_t* gen, int64_t offset)
{
  gen->i += PRNS_WEYL*((uint64_t)offset);
}

// returns the random number at position 'n'
static inline uint64_t prns_at(uint64_t n)
{
  return PRNS_MIX(PRNS_WEYL*n);
}

// returns the current random number without advancing the position
static inline uint64_t prns_peek(prns_t* gen)
{
  return PRNS_MIX(gen->i);
}

// return the current random number and advances the position by one
static inline uint64_t prns_next(prns_t* gen)
{
  uint64_t i = gen->i;
  uint64_t r = PRNS_MIX(i);
  gen->i = i + PRNS_WEYL;
  return r;
}

// return the current random number and moves the position by backward by one
static inline uint64_t prns_prev(prns_t* gen)
{
  uint64_t i = gen->i;
  uint64_t r = PRNS_MIX(i);
  gen->i = i - PRNS_WEYL;
  return r;
}

//**** "down" functions

static inline void prns_down_init(prns_down_t* d, prns_t* s)
{
  d->i = 0;
  d->k = s->i;
}

static inline uint64_t prns_down_tell(prns_down_t* gen)
{
  return gen->i * PRNS_WEYL_D_I;
}

static inline void prns_down_set(prns_down_t* gen, uint64_t pos)
{
  gen->i = PRNS_WEYL_D*pos;
}

static inline void prns_down_seek(prns_down_t* gen, int64_t offset)
{
  gen->i += PRNS_WEYL_D*((uint64_t)offset);
}

static inline uint64_t prns_down_at(prns_down_t* gen, uint64_t n)
{
  return PRNS_MIX_D(PRNS_WEYL_D*n, gen->k);
}

static inline uint64_t prns_down_peek(prns_down_t* gen)
{
  return PRNS_MIX_D(gen->i, gen->k);
}

static inline uint64_t prns_down_next(prns_down_t* gen)
{
  uint64_t i = gen->i;
  uint64_t r = PRNS_MIX_D(i, gen->k);
  gen->i = i + PRNS_WEYL_D;
  return r;
}

static inline uint64_t prns_down_prev(prns_down_t* gen)
{
  uint64_t i = gen->i;
  uint64_t r = PRNS_MIX_D(i, gen->k);
  gen->i = i - PRNS_WEYL_D;
  return r;
}

#endif