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
105 lines
2.8 KiB
Rust
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])
|
|
);
|
|
}
|
|
}
|