use super::Rng;
use super::c_stdlib::PackedBits;
#[derive(Debug, Clone, Copy)]
pub enum LcgVariant {
AnsiC,
Minstd,
Borland,
Msvc,
}
#[derive(Debug, Clone)]
pub struct Lcg32 {
state: u64,
a: u64,
c: u64,
m: u64,
shift: u32,
output_mask: u32,
raw_bits: u32,
bits: PackedBits,
}
impl Lcg32 {
pub fn new(variant: LcgVariant, seed: u64) -> Self {
let (state, a, c, m, shift, output_mask) = match variant {
LcgVariant::AnsiC => (seed & 0x7FFF_FFFF, 1_103_515_245, 12_345, 1 << 31, 0, u32::MAX),
LcgVariant::Minstd => (
if seed == 0 { 1 } else { seed % 2_147_483_647 },
16_807,
0,
2_147_483_647,
0,
u32::MAX,
),
LcgVariant::Borland => (seed & 0xFFFF_FFFF, 22_695_477, 1, 1u64 << 32, 16, 0x7FFF),
LcgVariant::Msvc => (seed & 0xFFFF_FFFF, 214_013, 2_531_011, 1u64 << 32, 16, 0x7FFF),
};
let raw_bits = output_mask.count_ones();
Self {
state,
a,
c,
m,
shift,
output_mask,
raw_bits,
bits: PackedBits::default(),
}
}
pub fn ansi_c() -> Self {
Self::new(LcgVariant::AnsiC, 1)
}
pub fn minstd() -> Self {
Self::new(LcgVariant::Minstd, 1)
}
pub fn next_raw(&mut self) -> u32 {
self.state = (self.a.wrapping_mul(self.state).wrapping_add(self.c)) % self.m;
((self.state >> self.shift) as u32) & self.output_mask
}
}
impl Rng for Lcg32 {
fn next_u32(&mut self) -> u32 {
if self.output_mask == u32::MAX {
return self.next_raw();
}
while self.bits.bits < 32 {
let raw = self.next_raw();
self.bits.push(raw, self.raw_bits);
}
self.bits.pop_word()
}
}
#[cfg(test)]
mod tests {
use super::{Lcg32, LcgVariant};
use crate::rng::Rng;
#[test]
fn msvc_raw_matches_known_seed_1_prefix() {
let mut rng = Lcg32::new(LcgVariant::Msvc, 1);
let got: Vec<u32> = (0..5).map(|_| rng.next_raw()).collect();
assert_eq!(got, vec![41, 18_467, 6_334, 26_500, 19_169]);
}
#[test]
fn msvc_next_u32_packs_raws_to_full_width() {
let mut rng = Lcg32::new(LcgVariant::Msvc, 1);
let high_bits_seen = (0..256)
.map(|_| rng.next_u32())
.fold(0u32, |acc, w| acc | (w >> 16));
assert!(
high_bits_seen != 0,
"Lcg32::Msvc next_u32 left the high 16 bits zero — packing regression"
);
}
#[test]
fn borland_next_u32_packs_raws_to_full_width() {
let mut rng = Lcg32::new(LcgVariant::Borland, 1);
let high_bits_seen = (0..256)
.map(|_| rng.next_u32())
.fold(0u32, |acc, w| acc | (w >> 16));
assert!(
high_bits_seen != 0,
"Lcg32::Borland next_u32 left the high 16 bits zero — packing regression"
);
}
}