inari 1.0.0

A Rust implementation of interval arithmetic
Documentation
use crate::{interval::*, simd::*};

impl Interval {
    /// Rounds `self` to the closest integer toward $+∞$.
    ///
    /// The domain and the range of the point function are:
    ///
    /// | Domain | Range |
    /// | ------ | ----- |
    /// | $\R$   | $\Z$  |
    ///
    /// # Examples
    ///
    /// ```
    /// use inari::*;
    /// assert_eq!(const_interval!(0.2, 1.2).ceil(), const_interval!(1.0, 2.0));
    /// assert_eq!(const_interval!(0.8, 1.8).ceil(), const_interval!(1.0, 2.0));
    /// assert_eq!(const_interval!(-1.2, -0.2).ceil(), const_interval!(-1.0, 0.0));
    /// assert_eq!(const_interval!(-1.8, -0.8).ceil(), const_interval!(-1.0, 0.0));
    /// assert_eq!(Interval::EMPTY.ceil(), Interval::EMPTY);
    /// assert_eq!(Interval::ENTIRE.ceil(), Interval::ENTIRE);
    /// ```
    ///
    /// See also: [`Interval::floor`], [`Interval::trunc`].
    #[must_use]
    pub fn ceil(self) -> Self {
        // _mm_ceil_pd/_mm_floor_pd are slow, better to avoid shuffling them.
        // ceil([a, b]) = [-ceil(a); ceil(b)]
        let x = neg0(self.rep); // [a; b]
        let r = ceil(x); // [ceil(a); ceil(b)]
        Self { rep: neg0(r) }
    }

    /// Rounds `self` to the closest integer toward $-∞$.
    ///
    /// The domain and the range of the point function are:
    ///
    /// | Domain | Range |
    /// | ------ | ----- |
    /// | $\R$   | $\Z$  |
    ///
    /// # Examples
    ///
    /// ```
    /// use inari::*;
    /// assert_eq!(const_interval!(0.2, 1.2).floor(), const_interval!(0.0, 1.0));
    /// assert_eq!(const_interval!(0.8, 1.8).floor(), const_interval!(0.0, 1.0));
    /// assert_eq!(const_interval!(-1.2, -0.2).floor(), const_interval!(-2.0, -1.0));
    /// assert_eq!(const_interval!(-1.8, -0.8).floor(), const_interval!(-2.0, -1.0));
    /// assert_eq!(Interval::EMPTY.floor(), Interval::EMPTY);
    /// assert_eq!(Interval::ENTIRE.floor(), Interval::ENTIRE);
    /// ```
    ///
    /// See also: [`Interval::ceil`], [`Interval::trunc`].
    #[must_use]
    pub fn floor(self) -> Self {
        // floor([a, b]) = [-floor(a); floor(b)]
        let x = neg0(self.rep); // [a; b]
        let r = floor(x); // [floor(a); floor(b)]
        Self { rep: neg0(r) }
    }

    /// Rounds `self` to the closest integer, away from zero in case of ties.
    ///
    /// The domain and the range of the point function are:
    ///
    /// | Domain | Range |
    /// | ------ | ----- |
    /// | $\R$   | $\Z$  |
    ///
    /// # Examples
    ///
    /// ```
    /// use inari::*;
    /// assert_eq!(const_interval!(0.2, 1.2).round(), const_interval!(0.0, 1.0));
    /// assert_eq!(const_interval!(0.5, 1.5).round(), const_interval!(1.0, 2.0));
    /// assert_eq!(const_interval!(0.8, 1.8).round(), const_interval!(1.0, 2.0));
    /// assert_eq!(const_interval!(-1.2, -0.2).round(), const_interval!(-1.0, 0.0));
    /// assert_eq!(const_interval!(-1.5, -0.5).round(), const_interval!(-2.0, -1.0));
    /// assert_eq!(const_interval!(-1.8, -0.8).round(), const_interval!(-2.0, -1.0));
    /// assert_eq!(Interval::EMPTY.round(), Interval::EMPTY);
    /// assert_eq!(Interval::ENTIRE.round(), Interval::ENTIRE);
    /// ```
    ///
    /// See also: [`Interval::round_ties_to_even`].
    #[must_use]
    pub fn round(self) -> Self {
        Self {
            rep: round(self.rep),
        }
    }

    /// Rounds `self` to the closest integer, the even number in case of ties.
    ///
    /// The domain and the range of the point function are:
    ///
    /// | Domain | Range |
    /// | ------ | ----- |
    /// | $\R$   | $\Z$  |
    ///
    /// # Examples
    ///
    /// ```
    /// use inari::*;
    /// assert_eq!(const_interval!(0.2, 1.2).round_ties_to_even(), const_interval!(0.0, 1.0));
    /// assert_eq!(const_interval!(0.5, 1.5).round_ties_to_even(), const_interval!(0.0, 2.0));
    /// assert_eq!(const_interval!(0.8, 1.8).round_ties_to_even(), const_interval!(1.0, 2.0));
    /// assert_eq!(const_interval!(-1.2, -0.2).round_ties_to_even(), const_interval!(-1.0, 0.0));
    /// assert_eq!(const_interval!(-1.5, -0.5).round_ties_to_even(), const_interval!(-2.0, 0.0));
    /// assert_eq!(const_interval!(-1.8, -0.8).round_ties_to_even(), const_interval!(-2.0, -1.0));
    /// assert_eq!(Interval::EMPTY.round_ties_to_even(), Interval::EMPTY);
    /// assert_eq!(Interval::ENTIRE.round_ties_to_even(), Interval::ENTIRE);
    /// ```
    ///
    /// See also: [`Interval::round`].
    #[must_use]
    pub fn round_ties_to_even(self) -> Self {
        Self {
            rep: round_ties_to_even(self.rep),
        }
    }

    /// Returns the sign of `self`.
    ///
    /// The domain and the range of the point function are:
    ///
    /// | Domain | Range            |
    /// | ------ | ---------------- |
    /// | $\R$   | $\set{-1, 0, 1}$ |
    ///
    /// Note the difference in definition between [`f64::signum`] and this function;
    /// `+0.0_f64.signum()` and `-0.0_f64.signum()` return `+1.0` and `-1.0`, respectively,
    /// while the sign of zero is just zero.
    ///
    /// # Examples
    ///
    /// ```
    /// use inari::*;
    /// assert_eq!(const_interval!(-10.0, -0.1).sign(), const_interval!(-1.0, -1.0));
    /// assert_eq!(const_interval!(0.0, 0.0).sign(), const_interval!(0.0, 0.0));
    /// assert_eq!(const_interval!(0.1, 10.0).sign(), const_interval!(1.0, 1.0));
    /// assert_eq!(Interval::EMPTY.sign(), Interval::EMPTY);
    /// assert_eq!(Interval::ENTIRE.sign(), const_interval!(-1.0, 1.0));
    /// ```
    #[must_use]
    pub fn sign(self) -> Self {
        if self.is_empty() {
            return Self::EMPTY;
        }

        let zero = splat(0.0);
        let gt_zero_mask = gt(self.rep, zero);
        let lt_zero_mask = lt(self.rep, zero);
        // [-(a ≤ 0), b ≥ 0] = [-a ≥ 0; b ≥ 0]
        let one_or_zero = and(splat(1.0), gt_zero_mask);
        // [a ≥ 0, -(b ≤ 0)] = [-(-a ≤ 0); -(b ≤ 0)]
        let m_one_or_zero = and(splat(-1.0), lt_zero_mask);
        // Gives the same result as addition, but faster.
        let r = or(one_or_zero, m_one_or_zero);
        Self { rep: r }
    }

    /// Rounds `self` to the closest integer toward zero.
    ///
    /// The domain and the range of the point function are:
    ///
    /// | Domain | Range |
    /// | ------ | ----- |
    /// | $\R$   | $\Z$  |
    ///
    /// # Examples
    ///
    /// ```
    /// use inari::*;
    /// assert_eq!(const_interval!(0.2, 1.2).trunc(), const_interval!(0.0, 1.0));
    /// assert_eq!(const_interval!(0.8, 1.8).trunc(), const_interval!(0.0, 1.0));
    /// assert_eq!(const_interval!(-1.2, -0.2).trunc(), const_interval!(-1.0, 0.0));
    /// assert_eq!(const_interval!(-1.8, -0.8).trunc(), const_interval!(-1.0, 0.0));
    /// assert_eq!(Interval::EMPTY.trunc(), Interval::EMPTY);
    /// assert_eq!(Interval::ENTIRE.trunc(), Interval::ENTIRE);
    /// ```
    ///
    /// See also: [`Interval::ceil`], [`Interval::floor`].
    #[must_use]
    pub fn trunc(self) -> Self {
        Self {
            rep: trunc(self.rep),
        }
    }
}

macro_rules! impl_dec {
    // is_not_com(x, y) tests if f is not continuous at some point of x,
    // provided that x and y := f(x) are bounded and p_dac(f, x) holds,
    // in which case y is a singleton.
    // The boundedness of x and y are checked by the last statement.
    // In rounding functions, you can effectively check if an endpoint of x
    // is an integer by x.inf == y.inf or x.sup == y.sup.
    ($f:ident, $x:ident, $y:ident, $is_not_com:expr) => {
        #[doc = concat!("The decorated version of [`Interval::", stringify!($f), "`].")]
        ///
        /// A NaI is returned if `self` is NaI.
        #[must_use]
        pub fn $f(self) -> Self {
            if self.is_nai() {
                return self;
            }

            let $x = self.x;
            let $y = $x.$f();
            let d = if $y.is_empty() {
                Decoration::Trv
            } else if !$y.is_singleton() {
                Decoration::Def
            } else if $is_not_com {
                Decoration::Dac
            } else {
                Decoration::Com
            };
            Self::set_dec($y, d.min(self.d))
        }
    };
}

// https://www.ocf.berkeley.edu/~horie/rounding.html
impl DecInterval {
    // Discontinuities: ℤ.
    impl_dec!(ceil, x, y, x.sup_raw() == y.sup_raw()); // No need to check inf.
    impl_dec!(floor, x, y, x.inf_raw() == y.inf_raw()); // No need to check sup.

    // Discontinuities: {x + 0.5 ∣ x ∈ ℤ}.
    impl_dec!(round, x, y, {
        let abs_a = x.inf_raw().abs();
        let abs_b = x.sup_raw().abs();
        (abs_a - abs_a.trunc() == 0.5) || (abs_b - abs_b.trunc() == 0.5)
    });
    impl_dec!(round_ties_to_even, x, y, {
        let abs_a = x.inf_raw().abs();
        let abs_b = x.sup_raw().abs();
        (abs_a - abs_a.trunc() == 0.5) || (abs_b - abs_b.trunc() == 0.5)
    });

    // Discontinuities: {0}.
    impl_dec!(sign, x, y, x.inf_raw() == 0.0); // No need to check sup.

    // Discontinuities: ℤ ∖ {0}.
    impl_dec!(
        trunc,
        x,
        y,
        (x.inf_raw() != 0.0 && x.inf_raw() == y.inf_raw())
            || (x.sup_raw() != 0.0 && x.sup_raw() == y.sup_raw())
    );
}

#[cfg(test)]
mod tests {
    use crate::*;
    use DecInterval as DI;
    use Interval as I;

    #[test]
    fn empty() {
        assert!(I::EMPTY.ceil().is_empty());
        assert!(I::EMPTY.floor().is_empty());
        assert!(I::EMPTY.round().is_empty());
        assert!(I::EMPTY.round_ties_to_even().is_empty());
        assert!(I::EMPTY.sign().is_empty());
        assert!(I::EMPTY.trunc().is_empty());

        assert!(DI::EMPTY.ceil().is_empty());
        assert!(DI::EMPTY.floor().is_empty());
        assert!(DI::EMPTY.round().is_empty());
        assert!(DI::EMPTY.round_ties_to_even().is_empty());
        assert!(DI::EMPTY.sign().is_empty());
        assert!(DI::EMPTY.trunc().is_empty());
    }

    #[test]
    fn nai() {
        assert!(DI::NAI.ceil().is_nai());
        assert!(DI::NAI.floor().is_nai());
        assert!(DI::NAI.round().is_nai());
        assert!(DI::NAI.round_ties_to_even().is_nai());
        assert!(DI::NAI.sign().is_nai());
        assert!(DI::NAI.trunc().is_nai());
    }
}