Skip to main content

dashu_float/
exp.rs

1use core::convert::TryInto;
2
3use crate::{
4    error::{assert_finite, assert_limited_precision, FpError, FpResult},
5    fbig::FBig,
6    math::cache::{reborrow_cache, ConstCache},
7    repr::{Context, Repr, Word},
8    round::Round,
9    utils::ceil_usize,
10};
11use dashu_base::{AbsOrd, Approximation::*, BitTest, DivRemEuclid, EstimatedLog2, Sign};
12use dashu_int::IBig;
13
14impl<R: Round, const B: Word> FBig<R, B> {
15    /// Raise the floating point number to an integer power.
16    ///
17    /// # Examples
18    ///
19    /// ```
20    /// # use dashu_base::ParseError;
21    /// # use dashu_float::DBig;
22    /// # use core::str::FromStr;
23    /// let a = DBig::from_str("-1.234")?;
24    /// assert_eq!(a.powi(10.into()), DBig::from_str("8.188")?);
25    /// # Ok::<(), ParseError>(())
26    /// ```
27    #[inline]
28    pub fn powi(&self, exp: IBig) -> FBig<R, B> {
29        self.context.unwrap_fp(self.context.powi(&self.repr, exp))
30    }
31
32    /// Raise the floating point number to an floating point power.
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// # use dashu_base::ParseError;
38    /// # use dashu_float::DBig;
39    /// # use core::str::FromStr;
40    /// let x = DBig::from_str("1.23")?;
41    /// let y = DBig::from_str("-4.56")?;
42    /// assert_eq!(x.powf(&y), DBig::from_str("0.389")?);
43    /// # Ok::<(), ParseError>(())
44    /// ```
45    #[inline]
46    pub fn powf(&self, exp: &Self) -> Self {
47        let context = Context::max(self.context, exp.context);
48        context.unwrap_fp(context.powf(&self.repr, &exp.repr, None))
49    }
50
51    /// Calculate the exponential function (`eˣ`) on the floating point number.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// # use dashu_base::ParseError;
57    /// # use dashu_float::DBig;
58    /// # use core::str::FromStr;
59    /// let a = DBig::from_str("-1.234")?;
60    /// assert_eq!(a.exp(), DBig::from_str("0.2911")?);
61    /// # Ok::<(), ParseError>(())
62    /// ```
63    #[inline]
64    pub fn exp(&self) -> FBig<R, B> {
65        self.context.unwrap_fp(self.context.exp(&self.repr, None))
66    }
67
68    /// Calculate the exponential minus one function (`eˣ-1`) on the floating point number.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// # use dashu_base::ParseError;
74    /// # use dashu_float::DBig;
75    /// # use core::str::FromStr;
76    /// let a = DBig::from_str("-0.1234")?;
77    /// assert_eq!(a.exp_m1(), DBig::from_str("-0.11609")?);
78    /// # Ok::<(), ParseError>(())
79    /// ```
80    #[inline]
81    pub fn exp_m1(&self) -> FBig<R, B> {
82        self.context
83            .unwrap_fp(self.context.exp_m1(&self.repr, None))
84    }
85}
86
87// TODO: give the exact formulation of required guard bits
88
89impl<R: Round> Context<R> {
90    /// Raise the floating point number to an integer power under this context.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// # use dashu_base::ParseError;
96    /// # use dashu_float::DBig;
97    /// # use core::str::FromStr;
98    /// use dashu_base::Approximation::*;
99    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
100    ///
101    /// let context = Context::<HalfAway>::new(2);
102    /// let a = DBig::from_str("-1.234")?;
103    /// assert_eq!(context.powi(&a.repr(), 10.into()), Ok(Inexact(DBig::from_str("8.2")?, AddOne)));
104    /// # Ok::<(), ParseError>(())
105    /// ```
106    ///
107    /// # Panics
108    ///
109    /// Panics if the precision is unlimited and the exponent is negative. In this case, the exact
110    /// result is likely to have infinite digits.
111    pub fn powi<const B: Word>(&self, base: &Repr<B>, exp: IBig) -> FpResult<FBig<R, B>> {
112        if base.is_infinite() {
113            return Err(FpError::InfiniteInput);
114        }
115
116        let (exp_sign, exp) = exp.into_parts();
117        if exp_sign == Sign::Negative {
118            // if the exponent is negative, then negate the exponent
119            // note that do the inverse at last requires less guard bits
120            assert_limited_precision(self.precision); // TODO: we can allow this if the inverse is exact (only when significand is one?)
121
122            let guard_bits = self.precision.bit_len() * 2; // heuristic
123            let rev_context = Context::<R::Reverse>::new(self.precision + guard_bits);
124            let pow = rev_context.unwrap_fp(rev_context.powi(base, exp.into()));
125            let inv = rev_context.unwrap_fp_repr(rev_context.repr_div(Repr::one(), pow.repr));
126            let repr = self.repr_round(inv);
127            return Ok(repr.map(|v| FBig::new(v, *self)));
128        }
129        if exp.is_zero() {
130            return Ok(Exact(FBig::ONE));
131        } else if exp.is_one() {
132            let repr = self.repr_round_ref(base);
133            return Ok(repr.map(|v| FBig::new(v, *self)));
134        }
135
136        // Guard against exponent overflow for astronomically large results: the result
137        // magnitude has log2 ≈ exp·log2(base); if that exceeds the isize exponent range,
138        // return ±inf (|base| > 1) or 0 (|base| < 1) instead of overflowing mid-computation.
139        let base_log2 = base.log2_est() as f64;
140        let threshold = (isize::MAX as f64) * (B.log2_est() as f64);
141        let exp_f64 = i64::try_from(&exp).ok().map(|e| e as f64);
142        let overflows = match exp_f64 {
143            Some(e) => e * base_log2 > threshold,
144            None => base_log2 != 0.0, // exp doesn't fit i64: overflows unless |base| == 1
145        };
146        if overflows {
147            return if base_log2 > 0.0 {
148                Err(FpError::Overflow(if base.sign() == Sign::Negative {
149                    Sign::Negative
150                } else {
151                    Sign::Positive
152                }))
153            } else {
154                // |base| < 1 and exponent huge → underflow to signed zero
155                let underflow_sign = if base.sign() == Sign::Negative && exp.bit(0) {
156                    Sign::Negative
157                } else {
158                    Sign::Positive
159                };
160                Err(FpError::Underflow(underflow_sign))
161            };
162        }
163
164        let work_context = if self.is_limited() {
165            // increase working precision when the exponent is large
166            let guard_digits = exp.bit_len() + self.precision.bit_len(); // heuristic
167            Context::<R>::new(self.precision + guard_digits)
168        } else {
169            Context::<R>::new(0)
170        };
171
172        // binary exponentiation from left to right
173        let mut p = exp.bit_len() - 2;
174        let mut res = work_context.unwrap_fp(work_context.sqr(base));
175        loop {
176            if exp.bit(p) {
177                res = work_context.unwrap_fp(work_context.mul(res.repr(), base));
178            }
179            if p == 0 {
180                break;
181            }
182            p -= 1;
183            res = work_context.unwrap_fp(work_context.sqr(res.repr()));
184        }
185
186        Ok(res.with_precision(self.precision))
187    }
188
189    /// Raise the floating point number to an floating point power under this context.
190    ///
191    /// Note that this method will not rely on [FBig::powi] even if the `exp` is actually an integer.
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// # use dashu_base::ParseError;
197    /// # use dashu_float::DBig;
198    /// # use core::str::FromStr;
199    /// use dashu_base::Approximation::*;
200    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
201    ///
202    /// let context = Context::<HalfAway>::new(2);
203    /// let x = DBig::from_str("1.23")?;
204    /// let y = DBig::from_str("-4.56")?;
205    /// assert_eq!(context.powf(&x.repr(), &y.repr(), None), Ok(Inexact(DBig::from_str("0.39")?, AddOne)));
206    /// # Ok::<(), ParseError>(())
207    /// ```
208    ///
209    /// # Panics
210    ///
211    /// Panics if the precision is unlimited.
212    pub fn powf<const B: Word>(
213        &self,
214        base: &Repr<B>,
215        exp: &Repr<B>,
216        mut cache: Option<&mut ConstCache>,
217    ) -> FpResult<FBig<R, B>> {
218        if base.is_infinite() || exp.is_infinite() {
219            return Err(FpError::InfiniteInput);
220        }
221        assert_limited_precision(self.precision); // TODO: we can allow it if exp is integer
222
223        // shortcuts
224        if exp.is_pos_zero() || exp.is_neg_zero() {
225            // pow(x, ±0) = 1 for any base (IEEE 754 §9.2.1); `-0` is numerically zero, so it
226            // must take the same shortcut as `+0` (otherwise a negative base falls through to
227            // the OutOfDomain path below).
228            return Ok(Exact(FBig::ONE));
229        } else if exp.is_one() {
230            let repr = self.repr_round_ref(base);
231            return Ok(repr.map(|v| FBig::new(v, *self)));
232        } else if base.significand.is_zero() {
233            // With a *float* exponent the result on a zero base is the positive one — this
234            // matches the common float-pow convention (e.g. CPython: `(-0.0) ** y == 0.0`),
235            // which doesn't track the parity of the exponent:
236            //   pow(±0, y > 0) = +0,    pow(±0, y < 0) = +inf.
237            // For the sign-correct result (e.g. `pow(-0, odd) = -0`), use the integer-exponent
238            // [`powi`](Context::powi). Short-circuiting here also avoids the negative-base path.
239            return Ok(Exact(if exp.sign() == Sign::Negative {
240                FBig::new(Repr::infinity(), *self)
241            } else {
242                FBig::ZERO
243            }));
244        }
245        if base.sign() == Sign::Negative {
246            // TODO: we should allow negative base when exp is an integer
247            return Err(FpError::OutOfDomain);
248        }
249
250        // x^y = exp(y*ln(x)), use a simple rule for guard bits
251        let guard_digits = 10 + ceil_usize(self.precision.log2_est());
252        let work_context = Context::<R>::new(self.precision + guard_digits);
253
254        // ln and exp each consult/extend the shared cache; reborrows are sequential.
255        let ln_val = work_context.unwrap_fp(work_context.ln(base, reborrow_cache(&mut cache)));
256        let mul_val = work_context.unwrap_fp(work_context.mul(ln_val.repr(), exp));
257        let exp_val =
258            work_context.unwrap_fp(work_context.exp(mul_val.repr(), reborrow_cache(&mut cache)));
259        Ok(exp_val.with_precision(self.precision))
260    }
261
262    /// Calculate the exponential function (`eˣ`) on the floating point number under this context.
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// # use dashu_base::ParseError;
268    /// # use dashu_float::DBig;
269    /// # use core::str::FromStr;
270    /// use dashu_base::Approximation::*;
271    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
272    ///
273    /// let context = Context::<HalfAway>::new(2);
274    /// let a = DBig::from_str("-1.234")?;
275    /// assert_eq!(context.exp(&a.repr(), None), Ok(Inexact(DBig::from_str("0.29")?, NoOp)));
276    /// # Ok::<(), ParseError>(())
277    /// ```
278    #[inline]
279    pub fn exp<const B: Word>(
280        &self,
281        x: &Repr<B>,
282        cache: Option<&mut ConstCache>,
283    ) -> FpResult<FBig<R, B>> {
284        if x.is_infinite() {
285            return Ok(Exact(FBig::new(
286                match x.sign() {
287                    Sign::Positive => Repr::infinity(),
288                    Sign::Negative => Repr::zero(),
289                },
290                *self,
291            )));
292        }
293        self.exp_internal(x, false, cache)
294    }
295
296    /// Calculate the exponential minus one function (`eˣ-1`) on the floating point number under this context.
297    ///
298    /// # Examples
299    ///
300    /// ```
301    /// # use dashu_base::ParseError;
302    /// # use dashu_float::DBig;
303    /// # use core::str::FromStr;
304    /// use dashu_base::Approximation::*;
305    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
306    ///
307    /// let context = Context::<HalfAway>::new(2);
308    /// let a = DBig::from_str("-0.1234")?;
309    /// assert_eq!(context.exp_m1(&a.repr(), None), Ok(Inexact(DBig::from_str("-0.12")?, SubOne)));
310    /// # Ok::<(), ParseError>(())
311    /// ```
312    #[inline]
313    pub fn exp_m1<const B: Word>(
314        &self,
315        x: &Repr<B>,
316        cache: Option<&mut ConstCache>,
317    ) -> FpResult<FBig<R, B>> {
318        if x.is_infinite() {
319            return match x.sign() {
320                Sign::Positive => Ok(Exact(FBig::new(Repr::infinity(), *self))),
321                Sign::Negative => Ok(Exact(-FBig::ONE)), // exp_m1(−∞) = −1
322            };
323        }
324        self.exp_internal(x, true, cache)
325    }
326
327    // TODO: change reduction to (x - s log2) / 2ⁿ, so that the final powering is always base 2, and doesn't depends on powi.
328    //       the powering exp(r)^(2ⁿ) could be optimized by noticing (1+x)^2 - 1 = x^2 + 2x
329    //       consider this change after having a benchmark
330
331    fn exp_internal<const B: Word>(
332        &self,
333        x: &Repr<B>,
334        minus_one: bool,
335        mut cache: Option<&mut ConstCache>,
336    ) -> FpResult<FBig<R, B>> {
337        assert_finite(x);
338        assert_limited_precision(self.precision);
339        let input_sign = x.sign();
340
341        if x.significand.is_zero() {
342            // exp(±0) = 1; exp_m1(±0) = ±0 (IEEE 754 §9.2.1 preserves the sign of zero)
343            return match minus_one {
344                false => Ok(Exact(FBig::ONE)),
345                true => {
346                    let zero = if input_sign == Sign::Negative {
347                        FBig::new(Repr::neg_zero(), Context::new(0))
348                    } else {
349                        FBig::ZERO
350                    };
351                    Ok(Exact(zero))
352                }
353            };
354        }
355
356        // A simple algorithm:
357        // - let r = (x - s logB) / Bⁿ, where s = floor(x / logB), such that r < B⁻ⁿ.
358        // - if the target precision is p digits, then there're only about p/m terms in Tyler series
359        // - finally, exp(x) = Bˢ * exp(r)^(Bⁿ)
360        // - the optimal n is √p as given by MPFR
361
362        // Maclaurin series: exp(r) = 1 + Σ(rⁱ/i!)
363        // There will be about p/log_B(r) summations when calculating the series, to prevent
364        // loss of significance, we need about log_B(p) guard digits.
365        let series_guard_digits = ceil_usize(self.precision.log2_est() / B.log2_est()) + 2;
366
367        // Reduction power: the series value is later raised to Bⁿ, which amplifies its
368        // relative error by a factor of Bⁿ. So the series (and the squarings) must carry
369        // about n extra base-B digits for the result to come out correct to p digits. We
370        // use 2n for safety — this mirrors MPFR's working precision q = precy + 2·K + …
371        // (K ≈ √precy is MPFR's squaring count, the analogue of our n). The log_B(p)
372        // summation/squaring rounding terms are already covered by series_guard_digits.
373        let n = 1usize << (self.precision.bit_len() / 2);
374        let pow_guard_digits = 2 * n;
375        let work_precision;
376
377        // When minus_one is true and |x| < 1/B, the input is fed into the Maclaurin series without scaling
378        let no_scaling = minus_one && x.log2_est() < -B.log2_est();
379        let (s, n, r) = if no_scaling {
380            // if minus_one is true and x is already small (x < 1/B),
381            // then directly evaluate the Maclaurin series without scaling
382            if x.sign() == Sign::Negative {
383                // extra digits are required to prevent cancellation during the summation
384                work_precision = self.precision + 2 * series_guard_digits;
385            } else {
386                work_precision = self.precision + series_guard_digits;
387            }
388            let context = Context::<R>::new(work_precision);
389            (0, 0, FBig::new(context.repr_round_ref(x).value(), context))
390        } else {
391            work_precision = self.precision + series_guard_digits + pow_guard_digits;
392            let context = Context::<R>::new(work_precision);
393            let x = FBig::new(context.repr_round_ref(x).value(), context);
394            let logb = context.ln_base::<B>(reborrow_cache(&mut cache));
395            let (s, r) = x.div_rem_euclid(logb);
396
397            let s: isize = match s.try_into() {
398                Ok(v) => v,
399                Err(_) => {
400                    // |floor(x / ln B)| overflows isize — x is astronomically large, so the
401                    // result is an infinity (x → +∞) or underflows to the limit (x → −∞).
402                    return if input_sign == Sign::Positive {
403                        Err(FpError::Overflow(Sign::Positive))
404                    } else if minus_one {
405                        Ok(Exact(-FBig::ONE)) // exp_m1(−∞) = −1 (finite)
406                    } else {
407                        Err(FpError::Underflow(Sign::Positive)) // exp(−∞) = +0
408                    };
409                }
410            };
411            (s, n, r)
412        };
413
414        let r = r >> n as isize;
415        let mut factorial = IBig::ONE;
416        let mut pow = r.clone();
417        let mut sum = if no_scaling {
418            r.clone()
419        } else {
420            FBig::ONE + &r
421        };
422
423        let mut k = 2;
424        loop {
425            factorial *= k;
426            pow *= &r;
427
428            let increase = &pow / &factorial;
429            if increase.abs_cmp(&sum.sub_ulp()).is_le() {
430                break;
431            }
432            sum += increase;
433            k += 1;
434        }
435
436        if no_scaling {
437            Ok(sum.with_precision(self.precision))
438        } else if minus_one {
439            // Power at the series' working precision (it already carries the 2n guard
440            // digits that the Bⁿ powering amplifies away). The final "−1" can cancel at
441            // most ~1 leading digit here (the |x| < 1/B case is handled by no_scaling),
442            // which the same guard digits comfortably absorb.
443            let pow_ctx = Context::<R>::new(work_precision);
444            let v = pow_ctx.unwrap_fp(pow_ctx.powi(sum.repr(), Repr::<B>::BASE.pow(n).into()));
445            Ok(((v << s) - FBig::ONE).with_precision(self.precision))
446        } else {
447            let pow_ctx = Context::<R>::new(work_precision);
448            let v = pow_ctx.unwrap_fp(pow_ctx.powi(sum.repr(), Repr::<B>::BASE.pow(n).into()));
449            Ok((v << s).with_precision(self.precision))
450        }
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457    use crate::round::mode;
458
459    #[test]
460    fn test_exp_overflow_is_infinity() {
461        let ctx = Context::<mode::HalfEven>::new(53);
462        // exp(huge) overflows the isize exponent range -> Overflow at Context level.
463        // Need x large enough that floor(x/ln2) > isize::MAX, i.e. x > ~2^62.5.
464        let huge = Repr::new(IBig::from(1) << 63, 0);
465        assert_eq!(ctx.exp::<2>(&huge, None), Err(FpError::Overflow(Sign::Positive)));
466
467        // exp(huge negative) underflows to +0
468        let neg = Repr::new(-(IBig::from(1) << 63), 0);
469        assert_eq!(ctx.exp::<2>(&neg, None), Err(FpError::Underflow(Sign::Positive)));
470
471        // exp_m1(huge negative) -> -1 (a finite value, not an error)
472        let m1 = ctx.exp_m1::<2>(&neg, None).unwrap().value();
473        assert_eq!(m1, -FBig::<mode::HalfEven>::ONE);
474    }
475
476    #[test]
477    fn test_powf_zero_base() {
478        use crate::DBig;
479        // powf with a float exponent returns the *positive* result on a zero base
480        // (matching the common float-pow convention); use powi for the signed result.
481        let ctx = Context::<mode::HalfEven>::new(53);
482        // powf(-0, 2.0) = +0 (NOT -0)
483        let r = ctx
484            .powf::<2>(&Repr::<2>::neg_zero(), &Repr::new(2.into(), 0), None)
485            .unwrap()
486            .value();
487        assert!(r.repr().is_pos_zero(), "expected +0");
488        assert!(!r.repr().is_neg_zero(), "powf(-0, x) should be +0, not -0");
489        // powf(0, -1) = +inf
490        let r = ctx
491            .powf::<2>(&Repr::<2>::zero(), &Repr::new((-1i32).into(), 0), None)
492            .unwrap()
493            .value();
494        assert!(r.repr().is_infinite());
495        assert_eq!(r.repr().sign(), Sign::Positive);
496        // powi(-0, 3) = -0 (the sign-correct, integer-exponent variant)
497        let r = ctx
498            .powi::<2>(&Repr::<2>::neg_zero(), 3.into())
499            .unwrap()
500            .value();
501        assert!(r.repr().is_neg_zero());
502        let _ = DBig::ZERO;
503    }
504}