use crate::{ConfidenceLevel, ValidationError};
use num_traits::{Float, ToPrimitive};
use std::fmt;
#[derive(Clone, Debug)]
struct Interval<T> {
lower: T,
upper: T,
}
impl<T: Float> Interval<T> {
fn new(lower: T, upper: T) -> Result<Self, ValidationError> {
if lower.is_nan() || upper.is_nan() {
return Err(ValidationError::NaN);
}
if !lower.is_finite() || !upper.is_finite() {
return Err(ValidationError::Infinite);
}
if lower > upper {
return Err(ValidationError::UnorderedRange {
lower: lower.to_f64().unwrap(),
upper: upper.to_f64().unwrap(),
});
}
Ok(Self { lower, upper })
}
}
impl<T> fmt::Display for Interval<T>
where
T: Float + ToPrimitive,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let lower = self.lower.to_f64().ok_or(fmt::Error)?;
let upper = self.upper.to_f64().ok_or(fmt::Error)?;
match f.precision() {
Some(p) => write!(f, "[{lower:.p$}, {upper:.p$}]"),
None => write!(f, "[{lower:.3}, {upper:.3}]"),
}
}
}
#[derive(Clone, Debug)]
pub struct ConfidenceInterval<T> {
interval: Interval<T>,
confidence_level: ConfidenceLevel<T>,
}
impl<T> fmt::Display for ConfidenceInterval<T>
where
T: Float + ToPrimitive,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({:.2}%)", self.interval, self.confidence_level)
}
}
impl<T: Copy> ConfidenceInterval<T> {
pub fn level(&self) -> ConfidenceLevel<T> {
self.confidence_level
}
}
impl<T: Float> ConfidenceInterval<T> {
pub fn new(
lower: T,
upper: T,
confidence_level: ConfidenceLevel<T>,
) -> Result<Self, ValidationError> {
let interval = Interval::new(lower, upper)?;
Ok(Self {
interval,
confidence_level,
})
}
}
impl<T: Float> ConfidenceInterval<T> {
pub fn lower(&self) -> T {
self.interval.lower
}
pub fn upper(&self) -> T {
self.interval.upper
}
}
impl<T: Float> ConfidenceInterval<T> {
pub fn contains(&self, value: T) -> bool {
if value.is_nan() {
return false;
}
(value <= self.upper()) && (value >= self.lower())
}
pub fn width(&self) -> T {
self.upper() - self.lower()
}
pub fn centre(&self) -> T {
let two = T::one() + T::one();
(self.lower() + self.upper()) / two
}
pub fn radius(&self) -> T {
let two = T::one() + T::one();
self.width() / two
}
}
impl<T> ConfidenceInterval<T>
where
T: ToPrimitive,
{
pub fn to_f64(self) -> Option<ConfidenceInterval<f64>> {
Some(ConfidenceInterval {
interval: Interval {
lower: self.interval.lower.to_f64()?,
upper: self.interval.upper.to_f64()?,
},
confidence_level: self.confidence_level.to_f64()?,
})
}
pub fn to_f32(self) -> Option<ConfidenceInterval<f32>> {
Some(ConfidenceInterval {
interval: Interval {
lower: self.interval.lower.to_f32()?,
upper: self.interval.upper.to_f32()?,
},
confidence_level: self.confidence_level.to_f32()?,
})
}
}