use std::cmp::Ordering;
use std::fmt;
use std::ops::Neg;
use crate::base::interval::Interval;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Extended<T> {
NegInf,
Finite(T),
PosInf,
}
impl<T> Extended<T> {
pub const fn is_finite(&self) -> bool {
matches!(self, Extended::Finite(_))
}
pub const fn finite(&self) -> Option<&T> {
match self {
Extended::Finite(v) => Some(v),
Extended::NegInf | Extended::PosInf => None,
}
}
pub fn into_finite(self) -> Option<T> {
match self {
Extended::Finite(v) => Some(v),
Extended::NegInf | Extended::PosInf => None,
}
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Extended<U> {
match self {
Extended::Finite(v) => Extended::Finite(f(v)),
Extended::NegInf => Extended::NegInf,
Extended::PosInf => Extended::PosInf,
}
}
pub const fn as_ref(&self) -> Extended<&T> {
match self {
Extended::Finite(v) => Extended::Finite(v),
Extended::NegInf => Extended::NegInf,
Extended::PosInf => Extended::PosInf,
}
}
}
impl<T: Clone + Neg<Output = T>> Extended<T> {
#[must_use]
pub fn neg(&self) -> Self {
-self.clone()
}
}
impl<T: Neg> Neg for Extended<T> {
type Output = Extended<T::Output>;
fn neg(self) -> Self::Output {
match self {
Extended::Finite(v) => Extended::Finite(-v),
Extended::NegInf => Extended::PosInf,
Extended::PosInf => Extended::NegInf,
}
}
}
impl Extended<f64> {
pub fn from_f64(x: f64) -> Self {
if x == f64::INFINITY {
Extended::PosInf
} else if x == f64::NEG_INFINITY {
Extended::NegInf
} else {
Extended::Finite(x)
}
}
}
impl<T> From<T> for Extended<T> {
fn from(value: T) -> Self {
Extended::Finite(value)
}
}
impl<T: PartialOrd> PartialOrd for Extended<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use Extended::*;
match (self, other) {
(NegInf, NegInf) | (PosInf, PosInf) => Some(Ordering::Equal),
(NegInf, _) | (_, PosInf) => Some(Ordering::Less),
(PosInf, _) | (_, NegInf) => Some(Ordering::Greater),
(Finite(a), Finite(b)) => a.partial_cmp(b),
}
}
}
impl<T: Ord> Ord for Extended<T> {
fn cmp(&self, other: &Self) -> Ordering {
use Extended::*;
match (self, other) {
(NegInf, NegInf) | (PosInf, PosInf) => Ordering::Equal,
(NegInf, _) | (_, PosInf) => Ordering::Less,
(PosInf, _) | (_, NegInf) => Ordering::Greater,
(Finite(a), Finite(b)) => a.cmp(b),
}
}
}
impl<T: fmt::Display> fmt::Display for Extended<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Extended::NegInf => f.write_str("-∞"),
Extended::Finite(v) => v.fmt(f),
Extended::PosInf => f.write_str("∞"),
}
}
}
impl<T> Interval<Extended<T>> {
pub const fn is_bounded(&self) -> bool {
self.lower.is_finite() && self.upper.is_finite()
}
}
#[cfg(test)]
mod tests {
use super::*;
use Extended::*;
#[test]
fn order_is_the_real_line_with_infinities_at_the_ends() {
assert!(NegInf < Finite(i64::MIN));
assert!(Finite(i64::MAX) < PosInf);
assert!(Finite(-5) < Finite(0) && Finite(0) < Finite(7));
assert!(Extended::<i32>::NegInf < PosInf);
assert_eq!(
NegInf.partial_cmp(&Extended::<i32>::NegInf),
Some(Ordering::Equal)
);
assert_eq!(
PosInf.partial_cmp(&Extended::<i32>::PosInf),
Some(Ordering::Equal)
);
assert_eq!(Finite(3).cmp(&Finite(3)), Ordering::Equal);
let mut v = vec![Finite(2), PosInf, Finite(-1), NegInf, Finite(0)];
v.sort();
assert_eq!(v, [NegInf, Finite(-1), Finite(0), Finite(2), PosInf]);
for a in &v {
for b in &v {
assert_eq!(a.partial_cmp(b), Some(a.cmp(b)));
}
}
}
#[test]
fn nan_is_unordered_and_finite() {
let nan = Extended::from_f64(f64::NAN);
assert!(nan.is_finite());
assert!(nan.partial_cmp(&Finite(0.0)).is_none());
assert!(
nan.partial_cmp(&PosInf).is_some(),
"an infinity beats even NaN"
);
assert_ne!(nan, nan);
}
#[test]
fn from_f64_maps_the_infinities() {
assert_eq!(Extended::from_f64(f64::INFINITY), PosInf);
assert_eq!(Extended::from_f64(f64::NEG_INFINITY), NegInf);
assert_eq!(Extended::from_f64(1.5), Finite(1.5));
assert_eq!(Extended::from_f64(f64::MAX), Finite(f64::MAX));
}
#[test]
fn helpers() {
let x = Finite(String::from("a"));
assert!(x.is_finite() && !Extended::<String>::PosInf.is_finite());
assert_eq!(x.finite().map(String::as_str), Some("a"));
assert_eq!(x.as_ref(), Finite(&String::from("a")));
assert_eq!(x.clone().into_finite(), Some(String::from("a")));
assert_eq!(Extended::<String>::NegInf.into_finite(), None);
assert_eq!(x.map(|s| s.len()), Finite(1));
assert_eq!(Extended::<String>::PosInf.map(|s| s.len()), PosInf);
assert_eq!(Extended::from(4), Finite(4));
}
#[test]
fn negation_swaps_the_infinities() {
assert_eq!(-Extended::<i32>::PosInf, NegInf);
assert_eq!(-Extended::<i32>::NegInf, PosInf);
assert_eq!(-Finite(3), Finite(-3));
assert_eq!(Finite(3).neg(), Finite(-3));
assert_eq!(Extended::<i32>::PosInf.neg(), NegInf);
}
#[test]
fn display() {
assert_eq!(Extended::<i32>::NegInf.to_string(), "-∞");
assert_eq!(Extended::<i32>::PosInf.to_string(), "∞");
assert_eq!(Finite(2.5).to_string(), "2.5");
assert_eq!(format!("{:>4}", Finite(7)), " 7");
}
#[test]
fn interval_of_extended_is_an_unbounded_interval() {
let ray = Interval::right_open(Finite(0), PosInf); assert!(ray.contains(&Finite(7)));
assert!(ray.contains(&Finite(0)));
assert!(!ray.contains(&Finite(-1)));
assert!(!ray.contains(&PosInf));
assert!(!ray.contains(&NegInf));
assert!(!ray.is_bounded() && ray.is_ordered() && !ray.is_empty());
assert_eq!(ray.to_string(), "[0, ∞)");
let line = Interval::open(NegInf, PosInf);
assert!(line.contains(&Finite(i32::MIN)) && line.contains(&Finite(i32::MAX)));
assert!(!line.is_bounded());
let seg = Interval::closed(Finite(1), Finite(2));
assert!(seg.is_bounded());
assert!(Interval::closed(Finite(3), Finite(3)).is_point());
assert!(Interval::closed(PosInf, Extended::<i32>::PosInf).is_point());
assert!(Interval::open(Finite(1), NegInf).is_empty());
}
}