commit 3ebded19da0147f65048c65954524b66b081ae61 Author: _ <_@_> Date: Mon Apr 7 21:06:00 2025 -0500 Rust implementation of PRNS diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ceb9ec4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "prns" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..82e7708 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "prns" +version = "1.0.0" +edition = "2024" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..03a532b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +A Rust implementation of the [PRNS pseudo-random number generator](https://marc-b-reynolds.github.io/shf/2016/04/19/prns.html). + +NOT cryptographically secure. Generates about 600,000 u64s per millisecond on a single thread of a 7th-gen Intel Core i5. diff --git a/cpp/.gitignore b/cpp/.gitignore new file mode 100644 index 0000000..1bc5040 --- /dev/null +++ b/cpp/.gitignore @@ -0,0 +1 @@ +prns_cpp diff --git a/cpp/build.bash b/cpp/build.bash new file mode 100755 index 0000000..64e029b --- /dev/null +++ b/cpp/build.bash @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +c++ -o prns_cpp main.cpp diff --git a/cpp/main.cpp b/cpp/main.cpp new file mode 100644 index 0000000..8672ef4 --- /dev/null +++ b/cpp/main.cpp @@ -0,0 +1,37 @@ +#include + +#include "prns.h" + +using namespace std; + +/* +Expected output from the original C version of PRNS: + +0 +16997136850213553216 +16093987548892232582 +7101631883897567084 +197735962506217616 +--- +5 +12951196477222847639 +12951196477222847639 +12951196477222847639 + */ + +int main() { + prns_t rng{0}; + + cerr << PRNS_MIX_S0 << endl; + + for(uint32_t i = 0; i < 5; i++) { + cerr << prns_next(&rng) << endl; + } + cerr << "---" << endl; + cerr << prns_tell(&rng) << endl; + cerr << prns_at(5) << endl; + cerr << prns_peek(&rng) << endl; + cerr << prns_prev(&rng) << endl; + + return 0; +} diff --git a/cpp/prns.h b/cpp/prns.h new file mode 100644 index 0000000..f8a0ae6 --- /dev/null +++ b/cpp/prns.h @@ -0,0 +1,262 @@ +// 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 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9ab2df9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,74 @@ +//! Implements the prns PRNG +//! +//! https://github.com/Marc-B-Reynolds/Stand-alone-junk/blob/master/src/SFH/prns.h + +use std::num::Wrapping; + +#[derive(Default)] +pub struct Prns { + i: Wrapping, +} + +const PRNS_MIX_S0: usize = 31; +const PRNS_MIX_S1: usize = 27; +const PRNS_MIX_S2: usize = 33; +const PRNS_MIX_M0: u64 = 0x7fb5d329728ea185; +const PRNS_MIX_M1: u64 = 0x81dadef4bc2dd44d; +const PRNS_WEYL: Wrapping = Wrapping(0x61c8864680b583eb); +const PRNS_WEYL_I: Wrapping = Wrapping(0x0e217c1e66c88cc3); + +impl Prns { + pub fn mix(mut x: Wrapping) -> u64 { + x ^= x >> PRNS_MIX_S0; + x *= PRNS_MIX_M0; + x ^= x >> PRNS_MIX_S1; + x *= PRNS_MIX_M1; + x ^= x >> PRNS_MIX_S2; + x.0 + } + + pub fn tell(&self) -> u64 { + (self.i * PRNS_WEYL_I).0 + } + + pub fn at(n: u64) -> u64 { + Self::mix(PRNS_WEYL * Wrapping(n)) + } + + pub fn peek(&self) -> u64 { + Self::mix(self.i) + } + + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> u64 { + let r = Self::mix(self.i); + self.i += PRNS_WEYL; + r + } + + pub fn prev(&mut self) -> u64 { + let r = Self::mix(self.i); + self.i -= PRNS_WEYL; + r + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let mut rng = Prns::default(); + assert_eq!(rng.next(), 0); + assert_eq!(rng.next(), 16997136850213553216); + assert_eq!(rng.next(), 16093987548892232582); + assert_eq!(rng.next(), 7101631883897567084); + assert_eq!(rng.next(), 197735962506217616); + + assert_eq!(rng.tell(), 5); + assert_eq!(Prns::at(5), 12951196477222847639); + assert_eq!(rng.peek(), 12951196477222847639); + assert_eq!(rng.prev(), 12951196477222847639); + } +}