Skip to main content

zenith_float_num/
ctx.rs

1//! Context is used in expressions returning `ExactNum`.
2
3use crate::Consts;
4use crate::Error;
5use crate::ExactNum;
6use crate::Exponent;
7use crate::RoundingMode;
8use crate::EXPONENT_MAX;
9use crate::EXPONENT_MIN;
10
11/// Context contains parameters, like rounding mode and precision, as well as constant values, and is used with `expr!` macro.
12#[derive(Debug)]
13pub struct Context {
14    cc: Consts,
15    p: usize,
16    rm: RoundingMode,
17    emin: Exponent,
18    emax: Exponent,
19}
20
21impl Context {
22    /// Create a new context.
23    /// The value of `emin` will be clamped to a range between EXPONENT_MIN and 0.
24    /// The value of `emax` will be clamped to a range between 0 and EXPONENT_MAX.
25    pub fn new(p: usize, rm: RoundingMode, cc: Consts, emin: Exponent, emax: Exponent) -> Self {
26        Context {
27            cc,
28            p,
29            rm,
30            emin: emin.clamp(EXPONENT_MIN, 0),
31            emax: emax.clamp(0, EXPONENT_MAX),
32        }
33    }
34
35    /// Destructures the context and returns its parts: target precision, rounding mode,
36    /// constant cache, minimum exponent, maximum exponent.
37    pub fn to_raw_parts(self) -> (usize, RoundingMode, Consts, Exponent, Exponent) {
38        let Context {
39            p,
40            rm,
41            cc,
42            emin,
43            emax,
44        } = self;
45        (p, rm, cc, emin, emax)
46    }
47
48    /// Sets the precision of the context.
49    pub fn set_precision(&mut self, p: usize) {
50        self.p = p;
51    }
52
53    /// Sets the rounding mode of the context.
54    pub fn set_rounding_mode(&mut self, rm: RoundingMode) {
55        self.rm = rm;
56    }
57
58    /// Sets the constant cache of the context.
59    pub fn set_consts(&mut self, cc: Consts) {
60        self.cc = cc;
61    }
62
63    /// Sets the minimum exponent.
64    /// The value of `emin` will be clamped to a range between EXPONENT_MIN and 0.
65    pub fn set_emin(&mut self, emin: Exponent) {
66        self.emin = emin.clamp(EXPONENT_MIN, 0);
67    }
68
69    /// Sets the maximum exponent.
70    /// The value of `emax` will be clamped to a range between 0 and EXPONENT_MAX.
71    pub fn set_emax(&mut self, emax: Exponent) {
72        self.emax = emax.clamp(0, EXPONENT_MAX);
73    }
74
75    /// Returns the precision of the context.
76    pub fn precision(&self) -> usize {
77        self.p
78    }
79
80    /// Returns the rounding mode of the context.
81    pub fn rounding_mode(&self) -> RoundingMode {
82        self.rm
83    }
84
85    /// Returns a mutable reference to the constant cache of the context.
86    pub fn consts(&mut self) -> &mut Consts {
87        &mut self.cc
88    }
89
90    /// Returns the value of the pi number.
91    pub fn const_pi(&mut self) -> ExactNum {
92        self.cc.pi(self.p, self.rm)
93    }
94
95    /// Returns the value of the Euler number.
96    pub fn const_e(&mut self) -> ExactNum {
97        self.cc.e(self.p, self.rm)
98    }
99
100    /// Returns the value of the natural logarithm of 2.
101    pub fn const_ln2(&mut self) -> ExactNum {
102        self.cc.ln_2(self.p, self.rm)
103    }
104
105    /// Returns the value of the natural logarithm of 10.
106    pub fn const_ln10(&mut self) -> ExactNum {
107        self.cc.ln_10(self.p, self.rm)
108    }
109
110    /// Returns √2.
111    pub fn const_sqrt2(&mut self) -> ExactNum {
112        self.cc.sqrt2(self.p, self.rm)
113    }
114
115    /// Returns the golden ratio φ.
116    pub fn const_phi(&mut self) -> ExactNum {
117        self.cc.phi(self.p, self.rm)
118    }
119
120    /// Returns the Euler–Mascheroni constant γ.
121    pub fn const_euler_gamma(&mut self) -> ExactNum {
122        self.cc.euler_gamma(self.p, self.rm)
123    }
124
125    /// Returns the minimum exponent.
126    pub fn emin(&self) -> Exponent {
127        self.emin
128    }
129
130    /// Returns the maximum exponent.
131    pub fn emax(&self) -> Exponent {
132        self.emax
133    }
134
135    /// Runs `f` with rounding mode `rm`, then restores the previous mode.
136    /// If `f` panics, the previous mode is not restored.
137    pub fn with_rounding_mode<F, R>(&mut self, rm: RoundingMode, f: F) -> R
138    where
139        F: FnOnce(&mut Self) -> R,
140    {
141        let old = self.rm;
142        self.rm = rm;
143        let out = f(self);
144        self.rm = old;
145        out
146    }
147
148    /// Clones `self` and returns the cloned context.
149    ///
150    /// # Errors
151    ///
152    /// - MemoryAllocation: failed to allocate memory for the constants cache.
153    #[allow(clippy::should_implement_trait)]
154    pub fn clone(&self) -> Result<Self, Error> {
155        let cc = Consts::new()?;
156        Ok(Context {
157            p: self.p,
158            rm: self.rm,
159            cc,
160            emin: self.emin,
161            emax: self.emax,
162        })
163    }
164}
165
166/// Represents a type that can be used as context in `expr!` macro.
167///
168/// ## Examples
169///
170/// ```
171/// # use zenith_float_num::RoundingMode;
172/// # use zenith_float_num::Consts;
173/// # use zenith_float_num::ctx::Contextable;
174/// let p = 123;
175/// let rm = RoundingMode::Down;
176/// let mut cc = Consts::new().expect("Constants cache allocated");
177/// let pi = cc.pi(p, rm);
178///
179/// // Make context out of tuple.
180/// let mut ctx = (p, rm, &mut cc);
181///
182/// assert_eq!(p, ctx.precision());
183/// assert_eq!(rm, ctx.rounding_mode());
184/// assert_eq!(pi, ctx.const_pi());
185/// ```
186pub trait Contextable {
187    /// Returns the precision of the context.
188    fn precision(&self) -> usize;
189
190    /// Returns the rounding mode of the context.
191    fn rounding_mode(&self) -> RoundingMode;
192
193    /// Returns a mutable reference to the constant cache of the context.
194    fn consts(&mut self) -> &mut Consts;
195
196    /// Returns the value of the pi number.
197    fn const_pi(&mut self) -> ExactNum;
198
199    /// Returns the value of the Euler number.
200    fn const_e(&mut self) -> ExactNum;
201
202    /// Returns the value of the natural logarithm of 2.
203    fn const_ln2(&mut self) -> ExactNum;
204
205    /// Returns the value of the natural logarithm of 10.
206    fn const_ln10(&mut self) -> ExactNum;
207
208    /// √2 at the context precision.
209    fn const_sqrt2(&mut self) -> ExactNum {
210        let p = self.precision();
211        let rm = self.rounding_mode();
212        self.consts().sqrt2(p, rm)
213    }
214
215    /// Golden ratio φ at the context precision.
216    fn const_phi(&mut self) -> ExactNum {
217        let p = self.precision();
218        let rm = self.rounding_mode();
219        self.consts().phi(p, rm)
220    }
221
222    /// Euler–Mascheroni constant γ at the context precision.
223    fn const_euler_gamma(&mut self) -> ExactNum {
224        let p = self.precision();
225        let rm = self.rounding_mode();
226        self.consts().euler_gamma(p, rm)
227    }
228
229    /// Returns the minimum exponent.
230    fn emin(&self) -> Exponent;
231
232    /// Returns the maximum exponent.
233    fn emax(&self) -> Exponent;
234}
235
236impl Contextable for (usize, RoundingMode, &mut Consts) {
237    fn precision(&self) -> usize {
238        self.0
239    }
240
241    fn rounding_mode(&self) -> RoundingMode {
242        self.1
243    }
244
245    fn consts(&mut self) -> &mut Consts {
246        self.2
247    }
248
249    fn const_pi(&mut self) -> ExactNum {
250        let (p, rm) = (self.0, self.1);
251        self.consts().pi(p, rm)
252    }
253
254    fn const_e(&mut self) -> ExactNum {
255        let (p, rm) = (self.0, self.1);
256        self.consts().e(p, rm)
257    }
258
259    fn const_ln2(&mut self) -> ExactNum {
260        let (p, rm) = (self.0, self.1);
261        self.consts().ln_2(p, rm)
262    }
263
264    fn const_ln10(&mut self) -> ExactNum {
265        let (p, rm) = (self.0, self.1);
266        self.consts().ln_10(p, rm)
267    }
268
269    fn emin(&self) -> Exponent {
270        EXPONENT_MIN
271    }
272
273    fn emax(&self) -> Exponent {
274        EXPONENT_MAX
275    }
276}
277
278impl Contextable for (usize, RoundingMode, &mut Consts, Exponent, Exponent) {
279    fn precision(&self) -> usize {
280        self.0
281    }
282
283    fn rounding_mode(&self) -> RoundingMode {
284        self.1
285    }
286
287    fn consts(&mut self) -> &mut Consts {
288        self.2
289    }
290
291    fn const_pi(&mut self) -> ExactNum {
292        let (p, rm) = (self.0, self.1);
293        self.consts().pi(p, rm)
294    }
295
296    fn const_e(&mut self) -> ExactNum {
297        let (p, rm) = (self.0, self.1);
298        self.consts().e(p, rm)
299    }
300
301    fn const_ln2(&mut self) -> ExactNum {
302        let (p, rm) = (self.0, self.1);
303        self.consts().ln_2(p, rm)
304    }
305
306    fn const_ln10(&mut self) -> ExactNum {
307        let (p, rm) = (self.0, self.1);
308        self.consts().ln_10(p, rm)
309    }
310
311    fn emin(&self) -> Exponent {
312        self.3.clamp(EXPONENT_MIN, 0)
313    }
314
315    fn emax(&self) -> Exponent {
316        self.4.clamp(0, EXPONENT_MAX)
317    }
318}
319
320impl Contextable for Context {
321    fn precision(&self) -> usize {
322        Context::precision(self)
323    }
324
325    fn rounding_mode(&self) -> RoundingMode {
326        Context::rounding_mode(self)
327    }
328
329    fn consts(&mut self) -> &mut Consts {
330        Context::consts(self)
331    }
332
333    fn const_pi(&mut self) -> ExactNum {
334        Context::const_pi(self)
335    }
336
337    fn const_e(&mut self) -> ExactNum {
338        Context::const_e(self)
339    }
340
341    fn const_ln2(&mut self) -> ExactNum {
342        Context::const_ln2(self)
343    }
344
345    fn const_ln10(&mut self) -> ExactNum {
346        Context::const_ln10(self)
347    }
348
349    fn const_sqrt2(&mut self) -> ExactNum {
350        Context::const_sqrt2(self)
351    }
352
353    fn const_phi(&mut self) -> ExactNum {
354        Context::const_phi(self)
355    }
356
357    fn const_euler_gamma(&mut self) -> ExactNum {
358        Context::const_euler_gamma(self)
359    }
360
361    fn emin(&self) -> Exponent {
362        Context::emin(self)
363    }
364
365    fn emax(&self) -> Exponent {
366        Context::emax(self)
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373    use crate::Consts;
374    use crate::RoundingMode;
375
376    #[test]
377    fn with_rounding_mode_restores() {
378        let mut ctx = Context::new(
379            128,
380            RoundingMode::ToEven,
381            Consts::new().unwrap(),
382            -1000,
383            1000,
384        );
385        let inner = ctx.with_rounding_mode(RoundingMode::Down, |c| c.rounding_mode());
386        assert_eq!(inner, RoundingMode::Down);
387        assert_eq!(ctx.rounding_mode(), RoundingMode::ToEven);
388    }
389}