Files
tdkpin/tdkpin-rs/src/borland_random.rs
T
ddidderr c2f1443436
Build TDK Pinball / build (macos-latest) (push) Canceled after 0s
Build TDK Pinball / build (ubuntu-latest) (push) Canceled after 0s
Build TDK Pinball / build (windows-latest) (push) Canceled after 0s
fmt: just fmt (rust only)
2026-08-29 09:57:39 +02:00

105 lines
2.8 KiB
Rust

//! 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])
);
}
}