refactor(algo): route the splitmix64 copies through one function (#7603)

* route the splitmix64 copies through one function

* document the splitmix64 generators

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Omar Saidahmed
2026-10-19 14:17:32 -04:00
committed by GitHub
parent 2ef8a9d3f5
commit cec0e843e6
4 changed files with 42 additions and 20 deletions
+2 -5
View File
@@ -45,12 +45,9 @@ pub const PATCH_RADIUS: usize = 15;
struct Rng(u64);
impl Rng {
/// The next value of the SplitMix64 sequence.
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
crate::photo_util::splitmix64(&mut self.0)
}
fn unit(&mut self) -> f64 {
(self.next() >> 11) as f64 / (1u64 << 53) as f64
+3 -5
View File
@@ -65,15 +65,13 @@ const SOURCE_MOMENTS_MAX: usize = 1 << 20;
#[derive(Clone)]
pub(crate) struct Rng(u64);
impl Rng {
/// A generator whose sequence is fixed by `seed`.
pub(crate) fn new(seed: u64) -> Self {
Rng(seed ^ 0x9E37_79B9_7F4A_7C15)
}
/// The next value of the SplitMix64 sequence.
pub(crate) fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
crate::photo_util::splitmix64(&mut self.0)
}
/// Skip `n` numbers (the same state as after `n` calls of [`Rng::next`]).
pub(crate) fn skip(&mut self, n: u64) {
+34 -5
View File
@@ -159,15 +159,21 @@ pub(crate) fn par_map<T: Send>(n: usize, f: impl Fn(usize) -> T + Sync + Send) -
(0..n).map(f).collect()
}
/// Advances `state` one SplitMix64 step and returns the mixed output.
pub(crate) fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
/// Deterministic 64-bit generator (SplitMix64).
pub(crate) struct Rng(pub u64);
impl Rng {
/// The next value of the SplitMix64 sequence.
pub fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
splitmix64(&mut self.0)
}
pub fn below(&mut self, n: usize) -> usize {
(self.next() % n.max(1) as u64) as usize
@@ -209,6 +215,29 @@ pub(crate) fn catmull_rom(t: f32) -> [f32; 4] {
mod tests {
use super::*;
/// The shared step and [`Rng`] reproduce the inlined copies they replaced, bit for bit.
#[test]
fn splitmix64_output_is_unchanged() {
/// The SplitMix64 step as each generator inlined it before.
fn inlined(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
let mut zero = 0;
assert_eq!([0; 3].map(|_| splitmix64(&mut zero)), [0xE220_A839_7B1D_CDAF, 0x6E78_9E6A_A1B9_65F4, 0x06C4_5D18_8009_454F]);
for seed in [0, 1, 42, 0x2545_F491_4F6C_DD1D, u64::MAX] {
let (mut shared, mut copy, mut rng) = (seed, seed, Rng(seed));
for _ in 0..10_000 {
let want = inlined(&mut copy);
assert_eq!(splitmix64(&mut shared), want);
assert_eq!(rng.next(), want);
}
}
}
/// The shared sRGB functions match the removed copies bit for bit.
#[test]
fn srgb_transfer_output_is_unchanged() {
+3 -5
View File
@@ -274,15 +274,13 @@ pub fn composite(surface: &mut Surface, prims: &[Prim], clip: Rect, selection: O
pub struct Rng(u64);
impl Rng {
/// A generator whose sequence is fixed by `seed` and `salt`.
pub fn new(seed: u32, salt: u64) -> Rng {
Rng(((seed as u64) << 32) ^ salt.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ 0x2545_F491_4F6C_DD1D)
}
/// The next value of the SplitMix64 sequence.
pub fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
crate::photo_util::splitmix64(&mut self.0)
}
/// Uniform in [0, 1).
pub fn f(&mut self) -> f32 {