use crate::types::Number;
use std::cell::Cell;
use std::sync::OnceLock;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
#[inline]
pub fn is_finite_number(val: Number) -> bool {
val.is_finite()
}
#[inline]
pub fn compare_le(lhs: Number, rhs: Number, bas_val: Number) -> bool {
let tol = 10.0 * Number::EPSILON * bas_val.abs().max(1.0);
lhs - rhs <= tol
}
pub fn ip_random_01() -> Number {
LCG_STATE.with(|s| {
let mut x = s.get();
x = x.wrapping_mul(LCG_A).wrapping_add(LCG_C) & LCG_MASK;
s.set(x);
let x0 = (x & 0xFFFF) as f64;
let x1 = ((x >> 16) & 0xFFFF) as f64;
let x2 = ((x >> 32) & 0xFFFF) as f64;
x2 / 65536.0 + x1 / (65536.0 * 65536.0) + x0 / (65536.0 * 65536.0 * 65536.0)
})
}
pub fn ip_reset_random_01() {
LCG_STATE.with(|s| s.set(LCG_DEFAULT_SEED));
}
const LCG_A: u64 = 0x5DEECE66D;
const LCG_C: u64 = 0xB;
const LCG_MASK: u64 = (1 << 48) - 1;
const LCG_DEFAULT_SEED: u64 = 0x1234_ABCD_330E;
thread_local! {
static LCG_STATE: Cell<u64> = const { Cell::new(LCG_DEFAULT_SEED) };
}
pub fn wallclock_time() -> Number {
static EPOCH: OnceLock<Instant> = OnceLock::new();
let e = EPOCH.get_or_init(Instant::now);
e.elapsed().as_secs_f64()
}
pub fn cpu_time() -> Number {
#[cfg(unix)]
{
let mut usage: libc::rusage = unsafe { core::mem::zeroed() };
if unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut usage) } == 0 {
return usage.ru_utime.tv_sec as Number + 1.0e-6 * usage.ru_utime.tv_usec as Number;
}
wallclock_time()
}
#[cfg(not(unix))]
{
wallclock_time()
}
}
pub fn sys_time() -> Number {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs_f64())
.unwrap_or(0.0)
}
pub fn compute_mem_increase_i64(
len: &mut i64,
recommended: f64,
min: i64,
context: &str,
) -> Result<(), String> {
if recommended >= i64::MAX as f64 {
if *len < i64::MAX {
*len = i64::MAX;
Ok(())
} else {
Err(format!(
"Cannot allocate more than {} bytes for {} due to integer-type limit",
(i64::MAX as f64) * 8.0,
context
))
}
} else {
*len = min.max(recommended as i64);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finite_check() {
assert!(is_finite_number(0.0));
assert!(is_finite_number(1e300));
assert!(!is_finite_number(f64::NAN));
assert!(!is_finite_number(f64::INFINITY));
}
#[test]
fn compare_le_tolerates_eps() {
let v = 1.0;
let near = v + 5.0 * Number::EPSILON;
assert!(compare_le(near, v, v));
}
#[test]
fn compare_le_floors_tolerance_for_small_basval() {
let bas_val = 1e-6;
let gap = 1e-18;
assert!(
compare_le(gap, 0.0, bas_val),
"gap {gap} is below the floored tolerance 10*eps*max(1, {bas_val}) \
and must be accepted; rejecting it reintroduces the Mittelmann \
tail stall"
);
let big = 1e-13;
assert!(!compare_le(big, 0.0, bas_val));
assert!(compare_le(5.0 * Number::EPSILON * 1e6, 0.0, 1e6));
assert!(!compare_le(3e-9, 0.0, 1e6)); }
#[test]
fn random_01_in_range_and_deterministic() {
ip_reset_random_01();
let a: Vec<f64> = (0..16).map(|_| ip_random_01()).collect();
ip_reset_random_01();
let b: Vec<f64> = (0..16).map(|_| ip_random_01()).collect();
assert_eq!(a, b);
for v in a {
assert!((0.0..1.0).contains(&v), "{v}");
}
}
#[test]
fn wallclock_monotonic() {
let a = wallclock_time();
let b = wallclock_time();
assert!(b >= a);
}
#[cfg(unix)]
#[test]
fn cpu_time_excludes_sleep_but_counts_compute() {
use std::hint::black_box;
use std::thread::sleep;
use std::time::Duration;
let cpu0 = cpu_time();
let wall0 = wallclock_time();
sleep(Duration::from_millis(300));
let wall_slept = wallclock_time() - wall0;
let cpu_slept = cpu_time() - cpu0;
assert!(
wall_slept - cpu_slept > 0.1,
"cpu_time advanced {:.3}s across a {:.3}s sleep; it must measure \
CPU, not wallclock (wall−cpu gap was only {:.3}s)",
cpu_slept,
wall_slept,
wall_slept - cpu_slept
);
let cpu1 = cpu_time();
let mut acc = 0u64;
for i in 0..50_000_000u64 {
acc = acc.wrapping_add(i ^ (i << 1));
}
black_box(acc);
let cpu_busy = cpu_time() - cpu1;
assert!(
cpu_busy > 0.0,
"cpu_time did not advance across a busy loop (got {:.6}s)",
cpu_busy
);
}
}