//! Borland Win16 random-number stream used by the original executable. use crate::real48::Real48; const MULTIPLIER: u32 = 0x0808_8405; #[cfg(test)] const TWO_TO_32: f64 = 4_294_967_296.0; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct BorlandRandom { seed: u32, } impl BorlandRandom { pub const fn new(seed: u32) -> Self { Self { seed } } pub fn next_u32(&mut self) -> u32 { self.seed = self.seed.wrapping_mul(MULTIPLIER).wrapping_add(1); self.seed } #[allow(clippy::cast_possible_truncation)] pub fn below(&mut self, upper_bound: u16) -> u16 { let product = u64::from(self.next_u32()) * u64::from(upper_bound); (product >> 32) as u16 } #[allow(clippy::cast_possible_truncation)] pub fn real48(&mut self) -> Real48 { let mut random = self.next_u32(); if random == 0 { return Real48::ZERO; } let mut exponent = 0x80_u8; while random & 0x8000_0000 == 0 { random <<= 1; exponent = exponent.wrapping_sub(1); } random &= 0x7fff_ffff; Real48::from_bytes([ exponent, 0, random as u8, (random >> 8) as u8, (random >> 16) as u8, (random >> 24) as u8, ]) } /// Exact host representation of the x87 `Random` result in `[0, 1)`. #[cfg(test)] pub fn unit_interval(&mut self) -> f64 { f64::from(self.next_u32()) / TWO_TO_32 } pub const fn seed(self) -> u32 { self.seed } } #[cfg(test)] mod tests { use super::*; #[test] fn stream_matches_the_reconstructed_borland_runtime() { let mut random = BorlandRandom::new(0x1234_5678); let first = 0x1234_5678_u32.wrapping_mul(MULTIPLIER).wrapping_add(1); assert_eq!(random.next_u32(), first); assert_eq!(random.below(3_800), 1_810); } #[test] fn zero_bounds_still_advance_the_seed() { let mut random = BorlandRandom::new(7); assert_eq!(random.below(0), 0); assert_eq!( random.seed(), 7_u32.wrapping_mul(MULTIPLIER).wrapping_add(1) ); } #[test] fn unit_interval_is_the_exact_unsigned_seed_fraction() { let mut random = BorlandRandom::new(0xfedc_ba98); let expected_seed = 0xfedc_ba98_u32.wrapping_mul(MULTIPLIER).wrapping_add(1); assert_eq!( random.unit_interval().to_bits(), (f64::from(expected_seed) / TWO_TO_32).to_bits() ); } #[test] fn real48_register_result_matches_the_reconstructed_normalization() { let mut random = BorlandRandom::new(7); assert_eq!( random.real48(), Real48::from_bytes([0x7e, 0, 0x90, 0x70, 0xee, 0x60]) ); } }