Skip to content

Commit cc285e9

Browse files
[rand_pcg v0.10.2] Add internal state fetching and construction (#108)
1 parent 2100f8f commit cc285e9

11 files changed

Lines changed: 137 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rand_pcg/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## 0.10.2 - 2026-04-11
8+
### Changed
9+
- Add methods to get the internal state of the generators (`state` and
10+
`stream` except for `Mcg128Xsl64`) and constructors to reproducibly
11+
restore them from that state (#108).
12+
713
## 0.10.1 - 2026-02-10
814
### Changed
915
- The crate is moved from [`rust-random/rand`] to [`rust-random/rngs`].

rand_pcg/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rand_pcg"
3-
version = "0.10.1"
3+
version = "0.10.2"
44
authors = ["The Rand Project Developers"]
55
license = "MIT OR Apache-2.0"
66
readme = "README.md"

rand_pcg/src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,42 @@ pub use rand_core;
9696
pub use self::pcg64::{Lcg64Xsh32, Pcg32};
9797
pub use self::pcg128::{Lcg128Xsl64, Mcg128Xsl64, Pcg64, Pcg64Mcg};
9898
pub use self::pcg128cm::{Lcg128CmDxsm64, Pcg64Dxsm};
99+
100+
mod macros {
101+
macro_rules! impl_state_stream {
102+
($type:ident, $int:ty) => {
103+
impl $type {
104+
/// Current state of the generator
105+
pub fn state(&self) -> $int {
106+
self.state
107+
}
108+
109+
/// Stream parameter of the generator
110+
///
111+
/// Note that PCG only stores an increment, which is always odd.
112+
/// [`Self::from_state`] discards the highest bit from the
113+
/// stream by shifting it to the left, so this method shifts the
114+
/// increment by one bit to the right.
115+
pub fn stream(&self) -> $int {
116+
self.increment >> 1
117+
}
118+
119+
/// Construct an instance using a pre-initialized `state`
120+
///
121+
/// Unlike [`Self::new`], this method does not mutate `state`.
122+
/// It may therefore be used with [`Self::state`] and
123+
/// [`Self::stream`] to reconstruct an RNG.
124+
///
125+
/// Note that the highest bit of the `stream` parameter is
126+
/// discarded to simplify upholding internal invariants.
127+
pub fn from_state(state: $int, stream: $int) -> Self {
128+
// The increment must be odd, hence we discard one bit:
129+
let increment = (stream << 1) | 1;
130+
$type { state, increment }
131+
}
132+
}
133+
};
134+
}
135+
136+
pub(crate) use impl_state_stream;
137+
}

rand_pcg/src/pcg128.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ impl Lcg128Xsl64 {
113113
}
114114
}
115115

116+
crate::macros::impl_state_stream!(Lcg128Xsl64, u128);
117+
116118
// Custom Debug implementation that does not expose the internal state
117119
impl fmt::Debug for Lcg128Xsl64 {
118120
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -217,6 +219,13 @@ impl Mcg128Xsl64 {
217219
// Force low bit to 1, as in C version (C++ uses `state | 3` instead).
218220
Mcg128Xsl64 { state: state | 1 }
219221
}
222+
223+
/// Current state of the generator
224+
///
225+
/// Will always be odd.
226+
pub fn state(&self) -> u128 {
227+
self.state
228+
}
220229
}
221230

222231
// Custom Debug implementation that does not expose the internal state

rand_pcg/src/pcg128cm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ impl Lcg128CmDxsm64 {
118118
}
119119
}
120120

121+
crate::macros::impl_state_stream!(Lcg128CmDxsm64, u128);
122+
121123
// Custom Debug implementation that does not expose the internal state
122124
impl fmt::Debug for Lcg128CmDxsm64 {
123125
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

rand_pcg/src/pcg64.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ impl Lcg64Xsh32 {
114114
}
115115
}
116116

117+
crate::macros::impl_state_stream!(Lcg64Xsh32, u64);
118+
117119
// Custom Debug implementation that does not expose the internal state
118120
impl fmt::Debug for Lcg64Xsh32 {
119121
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

rand_pcg/tests/lcg128cmdxsm64.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ fn test_lcg128cmdxsm64_reference() {
5454
assert_eq!(results, expected);
5555
}
5656

57+
#[test]
58+
fn test_lcg128cmdxsm64_advancing_restore() {
59+
let mut rng_orig = Lcg128CmDxsm64::new(1234, 567);
60+
61+
for _ in 0..100 {
62+
rng_orig.next_u64();
63+
}
64+
65+
let state = rng_orig.state();
66+
let stream = rng_orig.stream();
67+
let mut rng_copy = Lcg128CmDxsm64::from_state(state, stream);
68+
69+
for _ in 0..1_000 {
70+
assert_eq!(rng_copy.next_u64(), rng_orig.next_u64());
71+
}
72+
}
73+
5774
#[cfg(feature = "serde")]
5875
#[test]
5976
fn test_lcg128cmdxsm64_serde() {

rand_pcg/tests/lcg128xsl64.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@ fn test_lcg128xsl64_reference() {
5454
assert_eq!(results, expected);
5555
}
5656

57+
#[test]
58+
fn test_lcg128xsl64_advancing_restore() {
59+
let mut rng_orig = Lcg128Xsl64::new(1234, 567);
60+
61+
for _ in 0..100 {
62+
rng_orig.next_u64();
63+
}
64+
65+
let state = rng_orig.state();
66+
let stream = rng_orig.stream();
67+
let mut rng_copy = Lcg128Xsl64::from_state(state, stream);
68+
69+
for _ in 0..1_000 {
70+
assert_eq!(rng_copy.next_u64(), rng_orig.next_u64());
71+
}
72+
}
73+
5774
#[cfg(feature = "serde")]
5875
#[test]
5976
fn test_lcg128xsl64_serde() {

rand_pcg/tests/lcg64xsh32.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ fn test_lcg64xsh32_reference() {
4747
assert_eq!(results, expected);
4848
}
4949

50+
#[test]
51+
fn test_lcg64xsh32_advancing_restore() {
52+
let mut rng_orig = Lcg64Xsh32::new(1234, 567);
53+
54+
for _ in 0..100 {
55+
rng_orig.next_u64();
56+
}
57+
58+
let state = rng_orig.state();
59+
let stream = rng_orig.stream();
60+
let mut rng_copy = Lcg64Xsh32::from_state(state, stream);
61+
62+
for _ in 0..1_000 {
63+
assert_eq!(rng_copy.next_u64(), rng_orig.next_u64());
64+
}
65+
}
66+
5067
#[cfg(feature = "serde")]
5168
#[test]
5269
fn test_lcg64xsh32_serde() {

0 commit comments

Comments
 (0)