zenith-float-num 1.0.1

Software big-float kernel for zenith-float.
Documentation
//! Context is used in expressions returning `ExactNum`.

use crate::Consts;
use crate::Error;
use crate::ExactNum;
use crate::Exponent;
use crate::RoundingMode;
use crate::EXPONENT_MAX;
use crate::EXPONENT_MIN;

/// Context contains parameters, like rounding mode and precision, as well as constant values, and is used with `expr!` macro.
#[derive(Debug)]
pub struct Context {
    cc: Consts,
    p: usize,
    rm: RoundingMode,
    emin: Exponent,
    emax: Exponent,
}

impl Context {
    /// Create a new context.
    /// The value of `emin` will be clamped to a range between EXPONENT_MIN and 0.
    /// The value of `emax` will be clamped to a range between 0 and EXPONENT_MAX.
    pub fn new(p: usize, rm: RoundingMode, cc: Consts, emin: Exponent, emax: Exponent) -> Self {
        Context {
            cc,
            p,
            rm,
            emin: emin.clamp(EXPONENT_MIN, 0),
            emax: emax.clamp(0, EXPONENT_MAX),
        }
    }

    /// Destructures the context and returns its parts: target precision, rounding mode,
    /// constant cache, minimum exponent, maximum exponent.
    pub fn to_raw_parts(self) -> (usize, RoundingMode, Consts, Exponent, Exponent) {
        let Context {
            p,
            rm,
            cc,
            emin,
            emax,
        } = self;
        (p, rm, cc, emin, emax)
    }

    /// Sets the precision of the context.
    pub fn set_precision(&mut self, p: usize) {
        self.p = p;
    }

    /// Sets the rounding mode of the context.
    pub fn set_rounding_mode(&mut self, rm: RoundingMode) {
        self.rm = rm;
    }

    /// Sets the constant cache of the context.
    pub fn set_consts(&mut self, cc: Consts) {
        self.cc = cc;
    }

    /// Sets the minimum exponent.
    /// The value of `emin` will be clamped to a range between EXPONENT_MIN and 0.
    pub fn set_emin(&mut self, emin: Exponent) {
        self.emin = emin.clamp(EXPONENT_MIN, 0);
    }

    /// Sets the maximum exponent.
    /// The value of `emax` will be clamped to a range between 0 and EXPONENT_MAX.
    pub fn set_emax(&mut self, emax: Exponent) {
        self.emax = emax.clamp(0, EXPONENT_MAX);
    }

    /// Returns the precision of the context.
    pub fn precision(&self) -> usize {
        self.p
    }

    /// Returns the rounding mode of the context.
    pub fn rounding_mode(&self) -> RoundingMode {
        self.rm
    }

    /// Returns a mutable reference to the constant cache of the context.
    pub fn consts(&mut self) -> &mut Consts {
        &mut self.cc
    }

    /// Returns the value of the pi number.
    pub fn const_pi(&mut self) -> ExactNum {
        self.cc.pi(self.p, self.rm)
    }

    /// Returns the value of the Euler number.
    pub fn const_e(&mut self) -> ExactNum {
        self.cc.e(self.p, self.rm)
    }

    /// Returns the value of the natural logarithm of 2.
    pub fn const_ln2(&mut self) -> ExactNum {
        self.cc.ln_2(self.p, self.rm)
    }

    /// Returns the value of the natural logarithm of 10.
    pub fn const_ln10(&mut self) -> ExactNum {
        self.cc.ln_10(self.p, self.rm)
    }

    /// Returns √2.
    pub fn const_sqrt2(&mut self) -> ExactNum {
        self.cc.sqrt2(self.p, self.rm)
    }

    /// Returns the golden ratio φ.
    pub fn const_phi(&mut self) -> ExactNum {
        self.cc.phi(self.p, self.rm)
    }

    /// Returns the Euler–Mascheroni constant γ.
    pub fn const_euler_gamma(&mut self) -> ExactNum {
        self.cc.euler_gamma(self.p, self.rm)
    }

    /// Returns the minimum exponent.
    pub fn emin(&self) -> Exponent {
        self.emin
    }

    /// Returns the maximum exponent.
    pub fn emax(&self) -> Exponent {
        self.emax
    }

    /// Runs `f` with rounding mode `rm`, then restores the previous mode.
    /// If `f` panics, the previous mode is not restored.
    pub fn with_rounding_mode<F, R>(&mut self, rm: RoundingMode, f: F) -> R
    where
        F: FnOnce(&mut Self) -> R,
    {
        let old = self.rm;
        self.rm = rm;
        let out = f(self);
        self.rm = old;
        out
    }

    /// Clones `self` and returns the cloned context.
    ///
    /// # Errors
    ///
    /// - MemoryAllocation: failed to allocate memory for the constants cache.
    #[allow(clippy::should_implement_trait)]
    pub fn clone(&self) -> Result<Self, Error> {
        let cc = Consts::new()?;
        Ok(Context {
            p: self.p,
            rm: self.rm,
            cc,
            emin: self.emin,
            emax: self.emax,
        })
    }
}

/// Represents a type that can be used as context in `expr!` macro.
///
/// ## Examples
///
/// ```
/// # use zenith_float_num::RoundingMode;
/// # use zenith_float_num::Consts;
/// # use zenith_float_num::ctx::Contextable;
/// let p = 123;
/// let rm = RoundingMode::Down;
/// let mut cc = Consts::new().expect("Constants cache allocated");
/// let pi = cc.pi(p, rm);
///
/// // Make context out of tuple.
/// let mut ctx = (p, rm, &mut cc);
///
/// assert_eq!(p, ctx.precision());
/// assert_eq!(rm, ctx.rounding_mode());
/// assert_eq!(pi, ctx.const_pi());
/// ```
pub trait Contextable {
    /// Returns the precision of the context.
    fn precision(&self) -> usize;

    /// Returns the rounding mode of the context.
    fn rounding_mode(&self) -> RoundingMode;

    /// Returns a mutable reference to the constant cache of the context.
    fn consts(&mut self) -> &mut Consts;

    /// Returns the value of the pi number.
    fn const_pi(&mut self) -> ExactNum;

    /// Returns the value of the Euler number.
    fn const_e(&mut self) -> ExactNum;

    /// Returns the value of the natural logarithm of 2.
    fn const_ln2(&mut self) -> ExactNum;

    /// Returns the value of the natural logarithm of 10.
    fn const_ln10(&mut self) -> ExactNum;

    /// √2 at the context precision.
    fn const_sqrt2(&mut self) -> ExactNum {
        let p = self.precision();
        let rm = self.rounding_mode();
        self.consts().sqrt2(p, rm)
    }

    /// Golden ratio φ at the context precision.
    fn const_phi(&mut self) -> ExactNum {
        let p = self.precision();
        let rm = self.rounding_mode();
        self.consts().phi(p, rm)
    }

    /// Euler–Mascheroni constant γ at the context precision.
    fn const_euler_gamma(&mut self) -> ExactNum {
        let p = self.precision();
        let rm = self.rounding_mode();
        self.consts().euler_gamma(p, rm)
    }

    /// Returns the minimum exponent.
    fn emin(&self) -> Exponent;

    /// Returns the maximum exponent.
    fn emax(&self) -> Exponent;
}

impl Contextable for (usize, RoundingMode, &mut Consts) {
    fn precision(&self) -> usize {
        self.0
    }

    fn rounding_mode(&self) -> RoundingMode {
        self.1
    }

    fn consts(&mut self) -> &mut Consts {
        self.2
    }

    fn const_pi(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().pi(p, rm)
    }

    fn const_e(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().e(p, rm)
    }

    fn const_ln2(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().ln_2(p, rm)
    }

    fn const_ln10(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().ln_10(p, rm)
    }

    fn emin(&self) -> Exponent {
        EXPONENT_MIN
    }

    fn emax(&self) -> Exponent {
        EXPONENT_MAX
    }
}

impl Contextable for (usize, RoundingMode, &mut Consts, Exponent, Exponent) {
    fn precision(&self) -> usize {
        self.0
    }

    fn rounding_mode(&self) -> RoundingMode {
        self.1
    }

    fn consts(&mut self) -> &mut Consts {
        self.2
    }

    fn const_pi(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().pi(p, rm)
    }

    fn const_e(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().e(p, rm)
    }

    fn const_ln2(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().ln_2(p, rm)
    }

    fn const_ln10(&mut self) -> ExactNum {
        let (p, rm) = (self.0, self.1);
        self.consts().ln_10(p, rm)
    }

    fn emin(&self) -> Exponent {
        self.3.clamp(EXPONENT_MIN, 0)
    }

    fn emax(&self) -> Exponent {
        self.4.clamp(0, EXPONENT_MAX)
    }
}

impl Contextable for Context {
    fn precision(&self) -> usize {
        Context::precision(self)
    }

    fn rounding_mode(&self) -> RoundingMode {
        Context::rounding_mode(self)
    }

    fn consts(&mut self) -> &mut Consts {
        Context::consts(self)
    }

    fn const_pi(&mut self) -> ExactNum {
        Context::const_pi(self)
    }

    fn const_e(&mut self) -> ExactNum {
        Context::const_e(self)
    }

    fn const_ln2(&mut self) -> ExactNum {
        Context::const_ln2(self)
    }

    fn const_ln10(&mut self) -> ExactNum {
        Context::const_ln10(self)
    }

    fn const_sqrt2(&mut self) -> ExactNum {
        Context::const_sqrt2(self)
    }

    fn const_phi(&mut self) -> ExactNum {
        Context::const_phi(self)
    }

    fn const_euler_gamma(&mut self) -> ExactNum {
        Context::const_euler_gamma(self)
    }

    fn emin(&self) -> Exponent {
        Context::emin(self)
    }

    fn emax(&self) -> Exponent {
        Context::emax(self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Consts;
    use crate::RoundingMode;

    #[test]
    fn with_rounding_mode_restores() {
        let mut ctx = Context::new(
            128,
            RoundingMode::ToEven,
            Consts::new().unwrap(),
            -1000,
            1000,
        );
        let inner = ctx.with_rounding_mode(RoundingMode::Down, |c| c.rounding_mode());
        assert_eq!(inner, RoundingMode::Down);
        assert_eq!(ctx.rounding_mode(), RoundingMode::ToEven);
    }
}