use std::ops::RangeInclusive;
const TWO_POW_63: f64 = 9_223_372_036_854_775_808.0;
pub(crate) fn f64_to_i64(x: f64) -> Option<i64> {
if x.is_finite() && x.trunc() == x && (-TWO_POW_63..TWO_POW_63).contains(&x) {
Some(x as i64)
} else {
None
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SpanError {
NotFinite,
OutOfRange,
TooMany,
}
pub(crate) fn integer_span(
t_min: f64,
t_max: f64,
widen: u32,
max_len: u64,
) -> Result<RangeInclusive<i64>, SpanError> {
if !t_min.is_finite() || !t_max.is_finite() {
return Err(SpanError::NotFinite);
}
let widen = i64::from(widen);
let lo = f64_to_i64(t_min.floor())
.and_then(|n| n.checked_sub(widen))
.ok_or(SpanError::OutOfRange)?;
let hi = f64_to_i64(t_max.ceil())
.and_then(|n| n.checked_add(widen))
.ok_or(SpanError::OutOfRange)?;
let len = i128::from(hi) - i128::from(lo) + 1;
if len > i128::from(max_len) {
return Err(SpanError::TooMany);
}
Ok(lo..=hi)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn f64_to_i64_is_exact_or_none() {
assert_eq!(f64_to_i64(0.0), Some(0));
assert_eq!(f64_to_i64(-0.0), Some(0));
assert_eq!(f64_to_i64(-3.0), Some(-3));
assert_eq!(f64_to_i64(2.5), None);
assert_eq!(f64_to_i64(f64::NAN), None);
assert_eq!(f64_to_i64(f64::INFINITY), None);
assert_eq!(f64_to_i64(f64::NEG_INFINITY), None);
assert_eq!(f64_to_i64(-TWO_POW_63), Some(i64::MIN));
assert_eq!(f64_to_i64(TWO_POW_63), None);
let below = f64::from_bits(TWO_POW_63.to_bits() - 1);
assert_eq!(f64_to_i64(below), Some(9_223_372_036_854_774_784));
assert_eq!(f64_to_i64(1e19), None);
assert_eq!(f64_to_i64(-1e19), None);
}
#[test]
fn integer_span_widens_and_refuses() {
assert_eq!(integer_span(0.5, 2.5, 1, 100), Ok(-1..=4));
assert_eq!(integer_span(-2.0, 2.0, 0, 100), Ok(-2..=2));
assert_eq!(integer_span(0.0, 9.0, 0, 10), Ok(0..=9));
assert_eq!(integer_span(0.0, 10.0, 0, 10), Err(SpanError::TooMany));
assert_eq!(
integer_span(f64::NAN, 1.0, 1, 10),
Err(SpanError::NotFinite)
);
assert_eq!(
integer_span(0.0, f64::INFINITY, 1, 10),
Err(SpanError::NotFinite)
);
assert_eq!(
integer_span(-1e19, 1e19, 1, 10_001),
Err(SpanError::OutOfRange)
);
assert_eq!(
integer_span(-9e18, 9e18, 1, 10_001),
Err(SpanError::TooMany)
);
assert_eq!(
integer_span(-TWO_POW_63, 0.0, 1, u64::MAX),
Err(SpanError::OutOfRange)
);
let full = integer_span(-TWO_POW_63, 9_223_372_036_854_774_784.0, 0, u64::MAX);
assert_eq!(full, Ok(i64::MIN..=9_223_372_036_854_774_784));
assert_eq!(integer_span(5.5, 1.5, 0, 10).map(Iterator::count), Ok(0));
}
}