use std::ops::{Bound, RangeBounds};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Interval {
min: f64,
max: f64,
min_closed: bool,
max_closed: bool,
}
impl Interval {
pub const fn new(min: f64, max: f64, min_closed: bool, max_closed: bool) -> Self {
Interval {
min,
max,
min_closed,
max_closed,
}
}
pub const fn closed(min: f64, max: f64) -> Self {
Self::new(min, max, true, true)
}
pub const fn open(min: f64, max: f64) -> Self {
Self::new(min, max, false, false)
}
pub const fn min(&self) -> f64 {
self.min
}
pub const fn max(&self) -> f64 {
self.max
}
pub const fn is_min_closed(&self) -> bool {
self.min_closed
}
pub const fn is_max_closed(&self) -> bool {
self.max_closed
}
pub const fn is_empty(&self) -> bool {
!(self.min < self.max || (self.min == self.max && self.min_closed && self.max_closed))
}
pub const fn contains(&self, value: f64) -> bool {
(value > self.min || (value == self.min && self.min_closed))
&& (value < self.max || (value == self.max && self.max_closed))
}
}
impl<R: RangeBounds<f64>> From<R> for Interval {
fn from(range: R) -> Self {
let end = |bound: Bound<&f64>, unbounded: f64| match bound {
Bound::Included(&value) => (value, true),
Bound::Excluded(&value) => (value, false),
Bound::Unbounded => (unbounded, false),
};
let (min, min_closed) = end(range.start_bound(), f64::NEG_INFINITY);
let (max, max_closed) = end(range.end_bound(), f64::INFINITY);
Self::new(min, max, min_closed, max_closed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ends_open_or_closed() {
let closed = Interval::closed(0.0, 5.0);
assert!(closed.contains(0.0) && closed.contains(5.0) && closed.contains(2.5));
assert!(!closed.contains(-0.1) && !closed.contains(5.1));
let open = Interval::open(0.0, 5.0);
assert!(!open.contains(0.0) && !open.contains(5.0) && open.contains(2.5));
let half = Interval::new(0.0, 5.0, true, false);
assert!(half.contains(0.0) && !half.contains(5.0));
assert!(!closed.contains(f64::NAN));
}
#[test]
fn emptiness() {
assert!(Interval::closed(5.0, 1.0).is_empty());
assert!(Interval::open(2.0, 2.0).is_empty());
assert!(Interval::new(2.0, 2.0, true, false).is_empty());
assert!(Interval::closed(1.0, f64::NAN).is_empty());
assert!(Interval::closed(f64::NAN, 1.0).is_empty());
assert!(!Interval::closed(2.0, 2.0).is_empty());
assert!(!Interval::open(f64::NEG_INFINITY, f64::INFINITY).is_empty());
}
#[test]
fn from_ranges() {
assert_eq!(Interval::from(0.0..5.0), Interval::new(0.0, 5.0, true, false));
assert_eq!(Interval::from(0.0..=5.0), Interval::closed(0.0, 5.0));
assert_eq!(Interval::from(2.0..), Interval::new(2.0, f64::INFINITY, true, false));
assert_eq!(Interval::from(..3.0), Interval::open(f64::NEG_INFINITY, 3.0));
assert_eq!(
Interval::from(..=3.0),
Interval::new(f64::NEG_INFINITY, 3.0, false, true)
);
assert_eq!(Interval::from(..), Interval::open(f64::NEG_INFINITY, f64::INFINITY));
assert_eq!(
Interval::from((Bound::Excluded(1.0), Bound::Included(5.0))),
Interval::new(1.0, 5.0, false, true)
);
assert!(!Interval::from(..).contains(f64::INFINITY));
}
}