portion 0.3.0

Dedicated wrappers for floating point numbers representing a portion of some numerical entity.
Documentation
/// The floating point type.
type Flt = f64;

/// A value in range [0..1].
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Portion(Flt);

/// The relative portion of one (numerator) within another (denominator).
/// Two segments are considered here : [0..denominator] and ]denominator..1].
/// If the numerator lies within first segment the resulting value is simply numerator/denumerator.
/// Otherwise the relative portion within the second segment is returned.
pub enum Within {
    /// Represents the portion within the interval [0..denominator].
    First(Portion),
    /// Represents the portion within the interval ]denominator..1].
    Second(Portion),
}

/// Any quantity that can be multiplied with a floating point value.
///
/// ```
/// use portion::f64::{ApplyPortion, Portion};
/// struct Speed(f64);
///
/// impl ApplyPortion for Speed {
///    fn apply_portion(self, portion: Portion) -> Self {
///       Self(self.0 * Portion::value(portion))
///    }
/// }
/// ```
pub trait ApplyPortion {
    /// * `portion` - A value in range [0..1]
    fn apply_portion(self, portion: Portion) -> Self;
}

impl Portion {
    /// The minimum value.
    ///
    /// ```
    /// use portion::f64::Portion;
    /// let p = Portion::zero();
    /// assert_eq!(Portion::value(p), 0.0);
    /// ```
    pub const fn zero() -> Self {
        Portion(0.0)
    }

    /// ```
    /// use portion::f64::Portion;
    /// let p = Portion::half();
    /// assert_eq!(Portion::value(p), 0.5);
    /// ```
    pub const fn half() -> Self {
        Portion(0.5)
    }

    /// The unit and maximum value.
    ///
    /// ```
    /// use portion::f64::Portion;
    /// let p = Portion::one();
    /// assert_eq!(Portion::value(p), 1.0);
    /// ```
    pub const fn one() -> Self {
        Portion(1.0)
    }

    /// Creates a portion at run time.
    ///
    /// ```
    /// use portion::f64::Portion;
    /// let p = Portion::new(-0.5);
    /// assert!(p.is_err());
    /// let p = Portion::new(0.0);
    /// assert!(p.is_ok());
    /// let p = Portion::new(0.5);
    /// assert!(p.is_ok());
    /// let p = Portion::new(1.0);
    /// assert!(p.is_ok());
    /// let p = Portion::new(1.5);
    /// assert!(p.is_err());
    /// for value in &[std::f64::NAN, std::f64::INFINITY, std::f64::NEG_INFINITY,] {
    ///    let p = Portion::new(*value);
    ///    assert!(p.is_err());
    /// }
    /// ```
    pub fn new(value: Flt) -> Result<Self, ()> {
        if 0.0 <= value && value <= 1. {
            Ok(Portion(value))
        } else {
            Err(())
        }
    }

    /// Returns a floating point value in range [0..1].
    ///
    /// This is intentionally not a method. Avoid it.
    ///
    /// ```
    /// use portion::f64::Portion;
    /// let p = Portion::new(0.25).unwrap();
    /// assert_eq!(Portion::value(p), 0.25);
    /// ```
    pub const fn value(p: Portion) -> Flt {
        p.0
    }

    /// Multiplies the portion with a scalar.
    ///
    /// Note that multiplying with another portion is more optimized.
    ///
    /// ```
    /// use portion::f64::Portion;
    /// let x = Portion::new(0.25).unwrap();
    /// let y = x.scale(2.0).unwrap();
    /// assert_eq!(Portion::value(y), 0.5);
    /// let y = x.scale(10.0);
    /// assert!(y.is_err());
    /// let y = x.scale(-1.0);
    /// assert!(y.is_err());
    /// ```
    pub fn scale(self, s: Flt) -> Result<Self, ()> {
        Self::new(self.0 * s)
    }

    /// Apply the portion to a certain quantity.
    ///
    /// This is the recommended way of applying a portion. The quantity type should follow the newtype idiom.
    ///
    /// ```
    /// use portion::f64::{ApplyPortion, Portion};
    /// struct Speed(f64);
    ///
    /// impl ApplyPortion for Speed {
    ///    fn apply_portion(self, portion: Portion) -> Self {
    ///       Self(self.0 * Portion::value(portion))
    ///    }
    /// }
    ///
    /// fn half_speed(speed: Speed) -> Speed {
    ///    Portion::half().apply(speed)
    /// }
    ///
    /// fn test_half_speed() {
    ///    let speed = half_speed(Speed(-16.0));
    ///    assert_eq!(speed.0, -8.0);
    /// }
    ///
    /// test_half_speed();
    /// ```
    pub fn apply<Q>(self, quantity: Q) -> Q
    where
        Q: ApplyPortion,
    {
        quantity.apply_portion(self)
    }

    /// Returns the difference to 1.
    ///
    /// ```
    /// use portion::f64::Portion;
    /// let x = Portion::new(0.25).unwrap();
    /// let y = x.complement();
    /// assert_eq!(Portion::value(y), 0.75);
    /// ```
    pub fn complement(self) -> Portion {
        Portion(1.0 - self.0)
    }

    /// The relative portion of one (numerator) within another (denominator).
    ///
    /// ```
    /// use portion::f64::{Portion, Within};
    /// let x = Portion::new(0.125).unwrap();
    /// let y = Portion::half();
    /// let z = x.within(y);
    /// if let Within::First(first) = z {
    ///    assert_eq!(Portion::value(first), 0.25);
    /// } else {
    ///    panic!("This should really lie in the first segment");
    /// }
    ///
    /// let x = Portion::new(0.875).unwrap();
    /// let y = Portion::half();
    /// let z = x.within(y);
    /// if let Within::Second(second) = z {
    ///    assert_eq!(Portion::value(second), 0.75);
    /// } else {
    ///    panic!("This should really lie in the second segment");
    /// }
    ///
    /// ```
    pub fn within(self, denominator: Self) -> Within {
        if self.0 <= denominator.0 {
            Within::First(Portion(self.0 / denominator.0))
        } else {
            Within::Second(Portion((self.0 - denominator.0) / (1.0 - denominator.0)))
        }
    }
}

impl std::ops::Mul for Portion {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self::Output {
        Portion(self.0.mul(rhs.0))
    }
}

impl std::ops::Mul<SPortion> for Portion {
    type Output = SPortion;
    fn mul(self, rhs: SPortion) -> Self::Output {
        SPortion(self.0.mul(rhs.0))
    }
}

impl std::ops::Add for Portion {
    type Output = Result<Self, ()>;
    fn add(self, rhs: Self) -> Self::Output {
        Portion::new(self.0.add(rhs.0))
    }
}

impl std::ops::Add<SPortion> for Portion {
    type Output = Result<SPortion, ()>;
    fn add(self, rhs: SPortion) -> Self::Output {
        SPortion::new(self.0.add(rhs.0))
    }
}

impl std::ops::Sub for Portion {
    type Output = SPortion;
    fn sub(self, rhs: Self) -> Self::Output {
        SPortion(self.0.sub(rhs.0))
    }
}

impl std::ops::Sub<SPortion> for Portion {
    type Output = Result<SPortion, ()>;
    fn sub(self, rhs: SPortion) -> Self::Output {
        SPortion::new(self.0.sub(rhs.0))
    }
}

impl std::ops::Neg for Portion {
    type Output = SPortion;
    fn neg(self) -> Self::Output {
        SPortion(self.0.neg())
    }
}

impl Eq for Portion {}

impl Ord for Portion {
    #[allow(clippy::float_cmp)]
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        if self.0 < other.0 {
            std::cmp::Ordering::Less
        } else if self.0 == other.0 {
            std::cmp::Ordering::Equal
        } else {
            std::cmp::Ordering::Greater
        }
    }
}

#[cfg(test)]
mod tests_portion {
    use super::*;

    #[test]
    fn test_within() {
        let x = Portion::new(0.75).unwrap();
        let y = Portion::zero();
        match x.within(y) {
            Within::Second(_) => {}
            _ => panic!("This should really lie in the second segment"),
        };

        let x = Portion::new(0.75).unwrap();
        let y = Portion::one();
        match x.within(y) {
            Within::First(_) => {}
            _ => panic!("This should really lie in the first segment"),
        };
    }

    #[test]
    fn test_mul_self() {
        let x = Portion::half();
        let y = x * x;
        assert_eq!(Portion::value(y), 0.25);
    }

    #[test]
    fn test_mul_negative() {
        let x = Portion::half();
        let y = -SPortion::half();
        let z = x * y;
        assert_eq!(SPortion::value(z), -0.25);
    }

    #[test]
    fn test_add_self() {
        let x = Portion::new(0.25).unwrap();
        let y = (x + x).unwrap();
        assert_eq!(Portion::value(y), 0.5);
        let x = Portion::new(0.75).unwrap();
        let y = x + x;
        assert!(y.is_err());
    }

    #[test]
    fn test_add_negative() {
        let x = Portion::new(0.25).unwrap();
        let y = SPortion::new(0.5).unwrap();
        let z = (x + y).unwrap();
        assert_eq!(SPortion::value(z), 0.75);
        let y = SPortion::new(-0.5).unwrap();
        let z = (x + y).unwrap();
        assert_eq!(SPortion::value(z), -0.25);
        let y = SPortion::new(0.9).unwrap();
        let z = x + y;
        assert!(z.is_err());
    }

    #[test]
    fn test_sub_self() {
        let x = Portion::new(0.25).unwrap();
        let y = Portion::new(0.5).unwrap();
        let z = x - y;
        assert_eq!(SPortion::value(z), -0.25);
    }

    #[test]
    fn test_sub_negative() {
        let x = Portion::new(0.25).unwrap();
        let y = SPortion::new(0.5).unwrap();
        let z = (x - y).unwrap();
        assert_eq!(SPortion::value(z), -0.25);
        let y = SPortion::new(-0.5).unwrap();
        let z = (x - y).unwrap();
        assert_eq!(SPortion::value(z), 0.75);
        let y = SPortion::new(-0.9).unwrap();
        let z = x - y;
        assert!(z.is_err());
    }

    #[test]
    fn test_neg() {
        let x: SPortion = -Portion::new(0.25).unwrap();
        assert_eq!(SPortion::value(x), -0.25);
    }

    #[test]
    fn test_ord() {
        let x = Portion::new(0.25).unwrap();
        let y = Portion::half();
        assert!(x < y);
        assert!(y > x);
        assert_eq!(x, x);
    }
}

/// A signed portion: a value in range [-1..1].
/// This type represents the difference between two unsigned portions.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct SPortion(Flt);

impl SPortion {
    /// The minimum value.
    ///
    /// ```
    /// use portion::f64::SPortion;
    /// let sp = SPortion::minus_one();
    /// assert_eq!(SPortion::value(sp), -1.0);
    /// ```
    pub const fn minus_one() -> Self {
        SPortion(-1.0)
    }

    /// The zero value.
    ///
    /// ```
    /// use portion::f64::SPortion;
    /// let sp = SPortion::zero();
    /// assert_eq!(SPortion::value(sp), 0.0);
    /// ```
    pub const fn zero() -> Self {
        SPortion(0.0)
    }

    /// ```
    /// use portion::f64::SPortion;
    /// let sp = SPortion::half();
    /// assert_eq!(SPortion::value(sp), 0.5);
    /// ```
    pub const fn half() -> Self {
        SPortion(0.5)
    }

    /// The unit and maximum value.
    ///
    /// ```
    /// use portion::f64::SPortion;
    /// let sp = SPortion::one();
    /// assert_eq!(SPortion::value(sp), 1.0);
    /// ```
    pub const fn one() -> Self {
        SPortion(1.0)
    }

    /// Creates a portion at run time.
    ///
    /// ```
    /// use portion::f64::SPortion;
    /// let sp = SPortion::new(-1.5);
    /// assert!(sp.is_err());
    /// let sp = SPortion::new(-1.0);
    /// assert!(sp.is_ok());
    /// let sp = SPortion::new(0.0);
    /// assert!(sp.is_ok());
    /// let sp = SPortion::new(1.0);
    /// assert!(sp.is_ok());
    /// let sp = SPortion::new(1.5);
    /// assert!(sp.is_err());
    /// for value in &[std::f64::NAN, std::f64::INFINITY, std::f64::NEG_INFINITY,] {
    ///    let sp = SPortion::new(*value);
    ///    assert!(sp.is_err());
    /// }
    /// ```
    pub fn new(value: Flt) -> Result<Self, ()> {
        if -1.0 <= value && value <= 1. {
            Ok(SPortion(value))
        } else {
            Err(())
        }
    }

    /// Returns a floating point value in range [-1..1].
    ///
    /// This is intentionally not a method. Avoid it.
    ///
    /// ```
    /// use portion::f64::SPortion;
    /// let sp = SPortion::new(-0.25).unwrap();
    /// assert_eq!(SPortion::value(sp), -0.25);
    /// ```
    pub const fn value(sp: SPortion) -> Flt {
        sp.0
    }

    /// Multiplies the portion with a scalar.
    ///
    /// Note that multiplying with another portion is more optimized.
    ///
    /// ```
    /// use portion::f64::SPortion;
    /// let x = SPortion::new(0.25).unwrap();
    /// let y = x.scale(2.0).unwrap();
    /// assert_eq!(SPortion::value(y), 0.5);
    /// let y = x.scale(10.0);
    /// assert!(y.is_err());
    /// let y = x.scale(-1.0).unwrap();
    /// assert_eq!(SPortion::value(y), -0.25);
    /// ```
    pub fn scale(self, s: Flt) -> Result<Self, ()> {
        Self::new(self.0 * s)
    }

    /// Converts to a positive portion, if the value is not negative.
    ///
    /// ```
    /// use portion::f64::{Portion, SPortion};
    /// let x = SPortion::half();
    /// let y = x.to_portion().unwrap();
    /// assert_eq!(Portion::value(y), 0.5);
    /// let x = SPortion::new(-0.5).unwrap();
    /// let y = x.to_portion();
    /// assert!(y.is_err());
    /// ```
    pub fn to_portion(self) -> Result<Portion, ()> {
        Portion::new(self.0)
    }

    /// Removes the value's sign.
    ///
    /// ```
    /// use portion::f64::{Portion, SPortion};
    /// let x = SPortion::half();
    /// let y = x.abs();
    /// assert_eq!(Portion::value(y), 0.5);
    /// let x = -SPortion::half();
    /// let y = x.abs();
    /// assert_eq!(Portion::value(y), 0.5);
    /// ```
    pub fn abs(self) -> Portion {
        Portion(Flt::abs(self.0))
    }
}

impl From<Portion> for SPortion {
    fn from(p: Portion) -> Self {
        SPortion(p.0)
    }
}

impl std::ops::Mul for SPortion {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self::Output {
        SPortion(self.0.mul(rhs.0))
    }
}

impl std::ops::Mul<Portion> for SPortion {
    type Output = Self;
    fn mul(self, rhs: Portion) -> Self::Output {
        SPortion(self.0.mul(rhs.0))
    }
}

impl std::ops::Add for SPortion {
    type Output = Result<Self, ()>;
    fn add(self, rhs: Self) -> Self::Output {
        SPortion::new(self.0.add(rhs.0))
    }
}

impl std::ops::Sub for SPortion {
    type Output = Result<Self, ()>;
    fn sub(self, rhs: Self) -> Self::Output {
        SPortion::new(self.0.sub(rhs.0))
    }
}

impl std::ops::Neg for SPortion {
    type Output = Self;
    fn neg(self) -> Self::Output {
        SPortion(self.0.neg())
    }
}

impl Eq for SPortion {}

impl Ord for SPortion {
    #[allow(clippy::float_cmp)]
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        if self.0 < other.0 {
            std::cmp::Ordering::Less
        } else if self.0 == other.0 {
            std::cmp::Ordering::Equal
        } else {
            std::cmp::Ordering::Greater
        }
    }
}

#[cfg(test)]
mod tests_dportion {
    use super::*;

    #[test]
    fn test_from_portion() {
        let x = Portion::half();
        let y: SPortion = x.into();
        assert_eq!(SPortion::value(y), 0.5);
    }

    #[test]
    fn test_mul() {
        let x = SPortion::half();
        let y = -x * x;
        assert_eq!(SPortion::value(y), -0.25);
    }

    #[test]
    fn test_mul_positive() {
        let x = -SPortion::half();
        let y = Portion::half();
        let z = x * y;
        assert_eq!(SPortion::value(z), -0.25);
    }

    #[test]
    fn test_add() {
        let x = SPortion::new(0.25).unwrap();
        let y = SPortion::new(-0.5).unwrap();
        let z = (x + y).unwrap();
        assert_eq!(SPortion::value(z), -0.25);
        let x = SPortion::new(0.75).unwrap();
        let y = x + x;
        assert!(y.is_err());
        let x = SPortion::new(-0.75).unwrap();
        let y = x + x;
        assert!(y.is_err());
    }

    #[test]
    fn test_sub() {
        let x = SPortion::new(0.25).unwrap();
        let y = SPortion::new(0.5).unwrap();
        let z = (x - y).unwrap();
        assert_eq!(SPortion::value(z), -0.25);
        let x = SPortion::new(0.75).unwrap();
        let y = SPortion::new(-0.75).unwrap();
        let z = x - y;
        assert!(z.is_err());
        let x = SPortion::new(-0.75).unwrap();
        let y = SPortion::new(0.75).unwrap();
        let z = x - y;
        assert!(z.is_err());
    }

    #[test]
    fn test_neg() {
        let x = -SPortion::new(0.25).unwrap();
        assert_eq!(SPortion::value(x), -0.25);
    }

    #[test]
    fn test_ord() {
        let x = -SPortion::half();
        let y = SPortion::half();
        assert!(x < y);
        assert!(y > x);
        assert_eq!(x, x);
    }
}

/*The code above is practically a copy pase of f32's where 32 is replaced by 64*/

use crate::f32 as F32;

impl From<F32::Portion> for Portion {
    fn from(p: F32::Portion) -> Self {
        Self(F32::Portion::value(p).into())
    }
}

impl From<F32::SPortion> for SPortion {
    fn from(sp: F32::SPortion) -> Self {
        Self(F32::SPortion::value(sp).into())
    }
}