use std::sync::{Mutex, PoisonError};
use rudb_common::{Error, LogicalType, Result, Value};
use rudb_vector::{Data, Vector};
struct Pcg32 {
state: u64,
}
const MULTIPLIER: u64 = 6_364_136_223_846_793_005;
const INCREMENT: u64 = 1_442_695_040_888_963_407;
impl Pcg32 {
fn seeded(seed: u64) -> Self {
Self { state: Self::bump(seed.wrapping_add(INCREMENT)) }
}
fn bump(state: u64) -> u64 {
state.wrapping_mul(MULTIPLIER).wrapping_add(INCREMENT)
}
#[expect(clippy::cast_possible_truncation, reason = "the output is the low 32 bits by design")]
fn next(&mut self) -> u32 {
let old = self.state;
self.state = Self::bump(old);
let shifted = (((old >> 18) ^ old) >> 27) as u32;
shifted.rotate_right((old >> 59) as u32)
}
fn next64(&mut self) -> u64 {
(u64::from(self.next()) << 32) | u64::from(self.next())
}
#[expect(clippy::cast_precision_loss, reason = "the rounding is the pin's too")]
fn double(&mut self) -> f64 {
self.next64() as f64 * (-64f64).exp2()
}
}
static SHARED: Mutex<Option<Pcg32>> = Mutex::new(None);
fn with_shared<T>(body: impl FnOnce(&mut Pcg32) -> T) -> T {
let mut shared = SHARED.lock().unwrap_or_else(PoisonError::into_inner);
body(shared.get_or_insert_with(|| Pcg32::seeded(entropy())))
}
fn entropy() -> u64 {
use std::hash::{BuildHasher, Hasher};
let mut hasher = std::collections::hash_map::RandomState::new().build_hasher();
hasher.write_u32(std::process::id());
if let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
hasher.write_u128(now.as_nanos());
}
hasher.finish()
}
pub fn random(rows: usize) -> Result<Vector> {
let mut own = Pcg32::seeded(with_shared(Pcg32::next64));
let values: Vec<f64> = (0..rows).map(|_| own.double()).collect();
Vector::flat(LogicalType::Double, Data::Float64(values.into()))
}
pub(crate) fn setseed(seed: &Value) -> Result<Value> {
let Value::Double(seed) = *seed else {
return Ok(Value::Null);
};
if !(-1.0..=1.0).contains(&seed) {
return Err(Error::invalid_input(
"SETSEED accepts seed values between -1.0 and 1.0, inclusive",
));
}
let half = f64::from(u32::MAX / 2);
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "0 to u32::MAX")]
let seed = ((seed + 1.0) * half) as u32;
with_shared(|shared| *shared = Pcg32::seeded(u64::from(seed)));
Ok(Value::Null)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_seeded_generator_repeats_the_pins_numbers() {
let mut shared = Pcg32::seeded(u64::from(((0.5 + 1.0) * f64::from(u32::MAX / 2)) as u32));
let mut own = Pcg32::seeded(shared.next64());
assert_eq!(own.double(), 0.851_113_188_628_732_5);
assert_eq!(own.double(), 0.564_860_018_730_782_4);
let mut second = Pcg32::seeded(shared.next64());
assert_eq!(second.double(), 0.002_978_387_269_385_594);
}
}