#![allow(clippy::excessive_precision)]
use super::UniformRng;
use crate::types::Real;
const WORD_BITS: u32 = 48;
const SHORT_LAG: usize = 10;
const LONG_LAG: usize = 24;
const WORD_MASK: u64 = (1u64 << WORD_BITS) - 1;
#[derive(Clone)]
struct SubtractWithCarry48 {
x: [u64; LONG_LAG],
carry: u64,
k: usize,
}
impl SubtractWithCarry48 {
fn new(seed: u64) -> Self {
let seed = if seed == 0 { 19_780_503 } else { seed };
let mut lcg = (seed % 2_147_483_563).max(1);
let mut next_lcg = || {
lcg = (40014 * lcg) % 2_147_483_563;
lcg
};
let mut x = [0u64; LONG_LAG];
for word in &mut x {
let lo = next_lcg();
let hi = next_lcg();
*word = (lo + (hi << 32)) & WORD_MASK;
}
let carry = u64::from(x[LONG_LAG - 1] == 0);
SubtractWithCarry48 { x, carry, k: 0 }
}
fn next(&mut self) -> u64 {
let short_index = (self.k + LONG_LAG - SHORT_LAG) % LONG_LAG;
let mut y = self.x[short_index] as i64 - self.x[self.k] as i64 - self.carry as i64;
if y < 0 {
y += 1i64 << WORD_BITS;
self.carry = 1;
} else {
self.carry = 0;
}
let y = y as u64;
self.x[self.k] = y;
self.k = (self.k + 1) % LONG_LAG;
y
}
}
#[derive(Clone)]
pub struct Ranlux64UniformRng<const P: usize, const R: usize> {
base: SubtractWithCarry48,
used: usize,
}
pub type Ranlux3UniformRng = Ranlux64UniformRng<223, 24>;
pub type Ranlux4UniformRng = Ranlux64UniformRng<389, 24>;
impl<const P: usize, const R: usize> Ranlux64UniformRng<P, R> {
pub fn new(seed: u64) -> Self {
Ranlux64UniformRng {
base: SubtractWithCarry48::new(seed),
used: 0,
}
}
fn next_u48(&mut self) -> u64 {
if self.used >= R {
for _ in 0..P - R {
self.base.next();
}
self.used = 0;
}
self.used += 1;
self.base.next()
}
}
impl<const P: usize, const R: usize> Default for Ranlux64UniformRng<P, R> {
fn default() -> Self {
Ranlux64UniformRng::new(19_780_503)
}
}
impl<const P: usize, const R: usize> UniformRng for Ranlux64UniformRng<P, R> {
fn next_real(&mut self) -> Real {
self.next_u48() as Real * (1.0 / (1u64 << WORD_BITS) as Real)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::comparison::close_enough;
#[test]
fn matches_quantlib_ranlux_sequences() {
let mut ranlux3 = Ranlux3UniformRng::new(2_938_723);
let mut ranlux4 = Ranlux4UniformRng::new(4_390_109);
let ranlux3_expected = [
0.307448851544538826,
0.666313657894363587,
0.698528013702823358,
0.0217381272445322793,
0.862964516238161394,
0.909193419106014034,
0.674484308686746914,
0.849607570377191479,
0.054626078713596371,
0.416474163715683687,
];
let ranlux4_expected = [
0.222209169374078641,
0.420181950405986271,
0.0302156663005135329,
0.0836259809475237148,
0.480549766594993599,
0.723472021829124401,
0.905819507194266293,
0.54072519936540786,
0.445908421479817463,
0.651084788437518824,
];
for _ in 0..10010 {
ranlux3.next_real();
ranlux4.next_real();
}
for i in 0..10 {
assert!(
close_enough(ranlux3.next_real(), ranlux3_expected[i]),
"ranlux3 mismatch at index {i}"
);
assert!(
close_enough(ranlux4.next_real(), ranlux4_expected[i]),
"ranlux4 mismatch at index {i}"
);
}
}
#[test]
fn seed_zero_selects_default_seed() {
let mut from_zero = Ranlux3UniformRng::new(0);
let mut from_default = Ranlux3UniformRng::new(19_780_503);
for i in 0..100 {
assert_eq!(
from_zero.next_u48(),
from_default.next_u48(),
"seed-0 sequence diverged from default seed at index {i}"
);
}
}
}