Skip to content

Instantly share code, notes, and snippets.

@ttappr
Last active July 8, 2025 17:20
Show Gist options
  • Save ttappr/4dbeb28b3499954cb96989c9654f89c6 to your computer and use it in GitHub Desktop.
Save ttappr/4dbeb28b3499954cb96989c9654f89c6 to your computer and use it in GitHub Desktop.
Linear congruential pseudorandom number generator
use std::time::{SystemTime, UNIX_EPOCH};
/// Multiplier used by the `SimpleRng` random number generator.
const RAND_MULTIPLIER: u128 = 6364136223846793005;
/// A simple variant of a random number generator.
///
pub struct SimpleRng(u128);
impl SimpleRng {
/// Creates a new RNG. If no seed value is provided, the RNG will be seeded
/// from the system clock with milliseconds since Unix epoch.
///
pub fn new(seed: Option<u64>) -> Self {
let seed = seed.unwrap_or_else(|| {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis()
as u64
});
Self(((seed as u128) << 64).wrapping_mul(RAND_MULTIPLIER)
.wrapping_add(1))
}
/// Produce a pseudorandom integer in the range [lo, hi).
///
pub fn randint(&mut self, lo: u64, hi: u64) -> u64 {
self.0 = self.0.wrapping_mul(RAND_MULTIPLIER).wrapping_add(1);
let range = (hi - lo) as u128;
let rand = self.0 >> 64;
(rand.wrapping_mul(range) >> 64) as u64 + lo
}
}
@ttappr
Copy link
Author

ttappr commented Jul 5, 2025

Using the upper 64 bits of the seed seems to result in RNG cycles with significantly larger periods. Formal tests using cycle detection should be run to confirm this. But from observations of this being used in simple applications, it appears to be a good approach. The source of the multiplier is this article.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment