#[inline]
pub(crate) fn encode(x: f32, max_code: u32) -> u32 {
(x.clamp(0.0, 1.0) * max_code as f32).round() as u32
}
#[inline]
pub(crate) fn decode(code: u32, max_code: u32) -> f32 {
code as f32 / max_code as f32
}
#[inline]
fn row_bits(row: usize, update_count: usize, chunk: usize) -> u64 {
let mut z = (row as u64)
.wrapping_mul(0x9E37_79B9_7F4A_7C15)
.wrapping_add((chunk as u64).wrapping_mul(0xC2B2_AE3D_27D4_EB4F))
.wrapping_add((update_count as u64).wrapping_mul(0x1656_67B1_9E37_79F9));
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub(crate) struct RowDraws {
row: usize,
update_count: usize,
bits: u64,
action: usize,
}
impl RowDraws {
#[inline]
pub(crate) fn new(row: usize, update_count: usize) -> Self {
Self {
row,
update_count,
bits: row_bits(row, update_count, 0),
action: 0,
}
}
#[inline]
pub(crate) fn next_u01(&mut self) -> f32 {
let (chunk, slot) = (self.action / 4, self.action % 4);
if self.action > 0 && slot == 0 {
self.bits = row_bits(self.row, self.update_count, chunk);
}
self.action += 1;
#[allow(clippy::cast_possible_truncation)]
let bits16 = (self.bits >> (16 * slot)) as u16;
f32::from(bits16) / 65_536.0
}
}
#[inline]
pub(crate) fn encode_stochastic(x: f32, max_code: u32, u01: f32) -> u32 {
let scaled = x.clamp(0.0, 1.0) * max_code as f32;
let floor = scaled.floor();
let frac = scaled - floor;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let base = floor as u32;
(base + u32::from(u01 < frac)).min(max_code)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn endpoints_and_midpoint_round_trip() {
let max = u16::MAX as u32;
assert_eq!(encode(0.0, max), 0);
assert_eq!(encode(1.0, max), max);
for &x in &[0.1f32, 0.25, 0.5, 0.7777, 0.999] {
let back = decode(encode(x, max), max);
assert!((x - back).abs() <= 0.5 / max as f32 + 1e-7, "{x} vs {back}");
}
}
#[test]
fn out_of_range_is_clamped() {
let max = u16::MAX as u32;
assert_eq!(encode(2.0, max), max);
assert_eq!(encode(-1.0, max), 0);
}
#[test]
fn row_draws_deterministic_and_in_range() {
for &(r, t) in &[(0usize, 1usize), (3, 999), (168, 67_000)] {
let mut a = RowDraws::new(r, t);
let mut b = RowDraws::new(r, t);
for _ in 0..8 {
let (x, y) = (a.next_u01(), b.next_u01());
assert_eq!(x.to_bits(), y.to_bits(), "same key ⇒ same stream");
assert!((0.0..1.0).contains(&x), "draw out of range: {x}");
}
}
assert_ne!(
RowDraws::new(0, 1).next_u01(),
RowDraws::new(0, 2).next_u01()
);
assert_ne!(
RowDraws::new(0, 1).next_u01(),
RowDraws::new(1, 1).next_u01()
);
}
#[test]
fn row_draws_are_unbiased_per_action() {
let max = u16::MAX as u32;
let x = 0.001_530_5_f32;
let scaled = f64::from(x.clamp(0.0, 1.0) * max as f32);
let n = 200_000u32;
for action in 0..4usize {
let mut sum = 0u64;
for t in 0..n {
let mut draws = RowDraws::new(7, t as usize);
let mut u = draws.next_u01();
for _ in 0..action {
u = draws.next_u01();
}
sum += u64::from(encode_stochastic(x, max, u));
}
let mean = sum as f64 / f64::from(n);
assert!(
(mean - scaled).abs() < 0.05,
"action {action} biased: mean {mean} vs true {scaled}"
);
}
}
#[test]
fn row_draws_are_independent_across_actions() {
let n = 100_000usize;
let mut below = [0u32; 4];
let mut joint = [[0u32; 4]; 4];
for t in 0..n {
let mut draws = RowDraws::new(3, t);
let u: Vec<f32> = (0..4).map(|_| draws.next_u01()).collect();
for a in 0..4 {
if u[a] < 0.5 {
below[a] += 1;
}
for b in (a + 1)..4 {
if u[a] < 0.5 && u[b] < 0.5 {
joint[a][b] += 1;
}
}
}
}
for a in 0..4 {
let p = f64::from(below[a]) / n as f64;
assert!((p - 0.5).abs() < 0.01, "action {a} marginal {p}");
for b in (a + 1)..4 {
let p = f64::from(joint[a][b]) / n as f64;
assert!((p - 0.25).abs() < 0.01, "pair ({a},{b}) joint {p}");
}
}
}
#[test]
fn encode_stochastic_endpoints_and_clamp() {
let max = u16::MAX as u32;
for &u in &[0.0f32, 0.5, 0.999_999] {
assert_eq!(encode_stochastic(0.0, max, u), 0);
assert_eq!(encode_stochastic(1.0, max, u), max);
assert_eq!(encode_stochastic(2.0, max, u), max); assert_eq!(encode_stochastic(-1.0, max, u), 0); }
}
#[test]
fn encode_stochastic_brackets_and_stays_in_range() {
let max = u16::MAX as u32;
for i in 0..1000u32 {
let x = i as f32 / 1000.0;
let floor = (x.clamp(0.0, 1.0) * max as f32).floor();
for &u in &[0.0f32, 0.3, 0.7, 0.999] {
let c = encode_stochastic(x, max, u);
assert!(c <= max, "code {c} exceeds max");
assert!(
c as f32 >= floor && c as f32 <= floor + 1.0,
"code {c} not adjacent to {floor}"
);
}
}
}
#[test]
fn encode_stochastic_is_unbiased() {
let max = u16::MAX as u32;
let x = 0.001_530_5_f32;
let scaled = (x.clamp(0.0, 1.0) * max as f32) as f64;
let n = 200_000u32;
let mut sum = 0u64;
for t in 0..n {
let u = RowDraws::new(7, t as usize).next_u01();
sum += u64::from(encode_stochastic(x, max, u));
}
let mean = sum as f64 / f64::from(n);
assert!(
(mean - scaled).abs() < 0.05,
"biased: mean {mean} vs true {scaled}"
);
}
}