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 // TODO: range handling has three known limitations at the exponent extremes:
113 // (1) the overflow guard below estimates the result magnitude with an f64 and misclassifies
114 // representable boundaries near 2^63; (2) genuine Overflow/Underflow is unwrapped mode-blindly
115 // to ±inf / signed zero; (3) the negative-exponent reciprocal path can panic. None affects
116 // ordinary inputs; fixing requires mode-aware range saturation.
117 if base.is_infinite() {
118 return Err(FpError::InfiniteInput);
119 }
120
121 let (exp_sign, exp) = exp.into_parts();
122 if exp_sign == Sign::Negative {
123 // if the exponent is negative, then negate the exponent
124 // note that do the inverse at last requires less guard bits
125 assert_limited_precision(self.precision); // TODO: we can allow this if the inverse is exact (only when significand is one?)
126
127 let guard_bits = self.precision.bit_len() * 2; // heuristic
128 let rev_context = Context::<R::Reverse>::new(self.precision + guard_bits);
129 let pow = rev_context.unwrap_fp(rev_context.powi(base, exp.into()));
130 let inv = rev_context.unwrap_fp_repr(rev_context.repr_div(Repr::one(), pow.repr));
131 let repr = self.repr_round(inv);
132 return Ok(repr.map(|v| FBig::new(v, *self)));
133 }
134 if exp.is_zero() {
135 return Ok(Exact(FBig::ONE));
136 } else if exp.is_one() {
137 let repr = self.repr_round_ref(base);
138 return Ok(repr.map(|v| FBig::new(v, *self)));
139 }
140
141 // Guard against exponent overflow/underflow for astronomically large results.
142 // The result magnitude has log2 ≈ exp·log2(base); if that leaves the isize
143 // exponent range, return ±inf (|base|>1) or signed 0 (|base|<1) instead of
144 // overflowing the exponent mid-computation.
145 //
146 // Use the *bounds* of log2(base), never the point estimate `log2_est`: when
147 // base is very close to 1 (a large significand with a large negative exponent),
148 // log2(base) is the difference of two ~1e3-magnitude terms and suffers
149 // catastrophic cancellation — `log2_est` returns ~1e-4 of f32 noise rather than
150 // ~0. Scaled by a large exponent that noise crosses the overflow threshold,
151 // which on 32-bit is only isize::MAX·log2(B) ≈ 7e9 (vs ≈3e19 on 64-bit), so the
152 // guard fires spuriously and returns ±inf — see issue #95. The bounds are
153 // derived from the exact significand bit length, so they don't cancel. Declare
154 // overflow only on the lower bound (no false positives) and underflow only on
155 // the upper bound (no false underflows); anything in between is computed.
156 let (base_log2_lb, base_log2_ub) = base.log2_bounds();
157 let base_log2_lb = base_log2_lb as f64;
158 let base_log2_ub = base_log2_ub as f64;
159 let threshold = (isize::MAX as f64) * (B.log2_est() as f64);
160 let exp_f64 = i64::try_from(&exp).ok().map(|e| e as f64);
161 let overflows = match exp_f64 {
162 Some(e) => e * base_log2_lb > threshold,
163 None => base_log2_lb > 0.0, // exp doesn't fit i64: overflows iff base > 1
164 };
165 if overflows {
166 return Err(FpError::Overflow(if base.sign() == Sign::Negative {
167 Sign::Negative
168 } else {
169 Sign::Positive
170 }));
171 }
172 let underflows = !base.significand.is_zero()
173 && match exp_f64 {
174 Some(e) => e * base_log2_ub < -threshold,
175 None => base_log2_ub < 0.0, // exp doesn't fit i64: underflows iff base < 1
176 };
177 if underflows {
178 // |base| < 1 and exponent huge → underflow to signed zero
179 let underflow_sign = if base.sign() == Sign::Negative && exp.bit(0) {
180 Sign::Negative
181 } else {
182 Sign::Positive
183 };
184 return Err(FpError::Underflow(underflow_sign));
185 }
186
187 let work_context = if self.is_limited() {
188 // increase working precision when the exponent is large
189 let guard_digits = exp.bit_len() + self.precision.bit_len(); // heuristic
190 Context::<R>::new(self.precision + guard_digits)
191 } else {
192 Context::<R>::new(0)
193 };
194
195 // binary exponentiation from left to right
196 let mut p = exp.bit_len() - 2;
197 let mut res = work_context.unwrap_fp(work_context.sqr(base));
198 loop {
199 if exp.bit(p) {
200 res = work_context.unwrap_fp(work_context.mul(res.repr(), base));
201 }
202 if p == 0 {
203 break;
204 }
205 p -= 1;
206 res = work_context.unwrap_fp(work_context.sqr(res.repr()));
207 }
208
209 Ok(res.with_precision(self.precision))
210 }
211
212 /// Raise the floating point number to an floating point power under this context.
213 ///
214 /// Note that this method will not rely on [FBig::powi] even if the `exp` is actually an integer.
215 ///
216 /// # Examples
217 ///
218 /// ```
219 /// # use dashu_base::ParseError;
220 /// # use dashu_float::DBig;
221 /// # use core::str::FromStr;
222 /// use dashu_base::Approximation::*;
223 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
224 ///
225 /// let context = Context::<HalfAway>::new(2);
226 /// let x = DBig::from_str("1.23")?;
227 /// let y = DBig::from_str("-4.56")?;
228 /// assert_eq!(context.powf(&x.repr(), &y.repr(), None), Ok(Inexact(DBig::from_str("0.39")?, AddOne)));
229 /// # Ok::<(), ParseError>(())
230 /// ```
231 ///
232 /// # Panics
233 ///
234 /// Panics if the precision is unlimited.
235 pub fn powf<const B: Word>(
236 &self,
237 base: &Repr<B>,
238 exp: &Repr<B>,
239 mut cache: Option<&mut ConstCache>,
240 ) -> FpResult<FBig<R, B>> {
241 if base.is_infinite() || exp.is_infinite() {
242 return Err(FpError::InfiniteInput);
243 }
244 assert_limited_precision(self.precision); // TODO: we can allow it if exp is integer
245
246 // shortcuts
247 if exp.is_pos_zero() || exp.is_neg_zero() {
248 // pow(x, ±0) = 1 for any base (IEEE 754 §9.2.1); `-0` is numerically zero, so it
249 // must take the same shortcut as `+0` (otherwise a negative base falls through to
250 // the OutOfDomain path below).
251 return Ok(Exact(FBig::ONE));
252 } else if exp.is_one() {
253 let repr = self.repr_round_ref(base);
254 return Ok(repr.map(|v| FBig::new(v, *self)));
255 } else if base.significand.is_zero() {
256 // With a *float* exponent the result on a zero base is the positive one — this
257 // matches the common float-pow convention (e.g. CPython: `(-0.0) ** y == 0.0`),
258 // which doesn't track the parity of the exponent:
259 // pow(±0, y > 0) = +0, pow(±0, y < 0) = +inf.
260 // For the sign-correct result (e.g. `pow(-0, odd) = -0`), use the integer-exponent
261 // [`powi`](Context::powi). Short-circuiting here also avoids the negative-base path.
262 return Ok(Exact(if exp.sign() == Sign::Negative {
263 FBig::new(Repr::infinity(), *self)
264 } else {
265 FBig::ZERO
266 }));
267 }
268 if base.sign() == Sign::Negative {
269 // TODO: we should allow negative base when exp is an integer
270 return Err(FpError::OutOfDomain);
271 }
272
273 // x^y = exp(y*ln(x)), use a simple rule for guard bits
274 let guard_digits = 10 + ceil_usize(self.precision.log2_est());
275 let work_context = Context::<R>::new(self.precision + guard_digits);
276
277 // ln and exp each consult/extend the shared cache; reborrows are sequential.
278 let ln_val = work_context.unwrap_fp(work_context.ln(base, reborrow_cache(&mut cache)));
279 let mul_val = work_context.unwrap_fp(work_context.mul(ln_val.repr(), exp));
280 let exp_val =
281 work_context.unwrap_fp(work_context.exp(mul_val.repr(), reborrow_cache(&mut cache)));
282 Ok(exp_val.with_precision(self.precision))
283 }
284
285 /// Calculate the exponential function (`eˣ`) on the floating point number under this context.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// # use dashu_base::ParseError;
291 /// # use dashu_float::DBig;
292 /// # use core::str::FromStr;
293 /// use dashu_base::Approximation::*;
294 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
295 ///
296 /// let context = Context::<HalfAway>::new(2);
297 /// let a = DBig::from_str("-1.234")?;
298 /// assert_eq!(context.exp(&a.repr(), None), Ok(Inexact(DBig::from_str("0.29")?, NoOp)));
299 /// # Ok::<(), ParseError>(())
300 /// ```
301 #[inline]
302 pub fn exp<const B: Word>(
303 &self,
304 x: &Repr<B>,
305 cache: Option<&mut ConstCache>,
306 ) -> FpResult<FBig<R, B>> {
307 if x.is_infinite() {
308 return Ok(Exact(FBig::new(
309 match x.sign() {
310 Sign::Positive => Repr::infinity(),
311 Sign::Negative => Repr::zero(),
312 },
313 *self,
314 )));
315 }
316 self.exp_internal(x, false, cache)
317 }
318
319 /// Calculate the exponential minus one function (`eˣ-1`) on the floating point number under this context.
320 ///
321 /// # Examples
322 ///
323 /// ```
324 /// # use dashu_base::ParseError;
325 /// # use dashu_float::DBig;
326 /// # use core::str::FromStr;
327 /// use dashu_base::Approximation::*;
328 /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
329 ///
330 /// let context = Context::<HalfAway>::new(2);
331 /// let a = DBig::from_str("-0.1234")?;
332 /// assert_eq!(context.exp_m1(&a.repr(), None), Ok(Inexact(DBig::from_str("-0.12")?, SubOne)));
333 /// # Ok::<(), ParseError>(())
334 /// ```
335 #[inline]
336 pub fn exp_m1<const B: Word>(
337 &self,
338 x: &Repr<B>,
339 cache: Option<&mut ConstCache>,
340 ) -> FpResult<FBig<R, B>> {
341 if x.is_infinite() {
342 return match x.sign() {
343 Sign::Positive => Ok(Exact(FBig::new(Repr::infinity(), *self))),
344 Sign::Negative => Ok(Exact(-FBig::ONE)), // exp_m1(−∞) = −1
345 };
346 }
347 self.exp_internal(x, true, cache)
348 }
349
350 // TODO: change reduction to (x - s log2) / 2ⁿ, so that the final powering is always base 2, and doesn't depends on powi.
351 // the powering exp(r)^(2ⁿ) could be optimized by noticing (1+x)^2 - 1 = x^2 + 2x
352 // consider this change after having a benchmark
353
354 fn exp_internal<const B: Word>(
355 &self,
356 x: &Repr<B>,
357 minus_one: bool,
358 mut cache: Option<&mut ConstCache>,
359 ) -> FpResult<FBig<R, B>> {
360 assert_finite(x);
361 let input_sign = x.sign();
362
363 if x.significand.is_zero() {
364 // exp(±0) = 1; exp_m1(±0) = ±0 (IEEE 754 §9.2.1 preserves the sign of zero).
365 // These exact results need no rounding, so handle them before the
366 // limited-precision assertion: a precision-0 (unlimited) FBig such as the
367 // one produced by `try_from(0.0)` must still compute exp/exp_m1 exactly.
368 return match minus_one {
369 false => Ok(Exact(FBig::ONE)),
370 true => {
371 let zero = if input_sign == Sign::Negative {
372 FBig::new(Repr::neg_zero(), Context::new(0))
373 } else {
374 FBig::ZERO
375 };
376 Ok(Exact(zero))
377 }
378 };
379 }
380
381 assert_limited_precision(self.precision);
382
383 // A simple algorithm:
384 // - let r = (x - s logB) / Bⁿ, where s = floor(x / logB), such that r < B⁻ⁿ.
385 // - if the target precision is p digits, then there're only about p/m terms in Tyler series
386 // - finally, exp(x) = Bˢ * exp(r)^(Bⁿ)
387 // - the optimal n is √p as given by MPFR
388
389 // Maclaurin series: exp(r) = 1 + Σ(rⁱ/i!)
390 // There will be about p/log_B(r) summations when calculating the series, to prevent
391 // loss of significance, we need about log_B(p) guard digits.
392 let series_guard_digits = ceil_usize(self.precision.log2_est() / B.log2_est()) + 2;
393
394 // Reduction power: the series value is later raised to Bⁿ, which amplifies its
395 // relative error by a factor of Bⁿ. So the series (and the squarings) must carry
396 // about n extra base-B digits for the result to come out correct to p digits. We
397 // use 2n for safety — this mirrors MPFR's working precision q = precy + 2·K + …
398 // (K ≈ √precy is MPFR's squaring count, the analogue of our n). The log_B(p)
399 // summation/squaring rounding terms are already covered by series_guard_digits.
400 let n = 1usize << (self.precision.bit_len() / 2);
401 let pow_guard_digits = 2 * n;
402 let work_precision;
403
404 // When minus_one is true and |x| < 1/B, the input is fed into the Maclaurin series without scaling
405 let no_scaling = minus_one && x.log2_est() < -B.log2_est();
406 let (s, n, r) = if no_scaling {
407 // if minus_one is true and x is already small (x < 1/B),
408 // then directly evaluate the Maclaurin series without scaling
409 if x.sign() == Sign::Negative {
410 // extra digits are required to prevent cancellation during the summation
411 work_precision = self.precision + 2 * series_guard_digits;
412 } else {
413 work_precision = self.precision + series_guard_digits;
414 }
415 let context = Context::<R>::new(work_precision);
416 (0, 0, FBig::new(context.repr_round_ref(x).value(), context))
417 } else {
418 work_precision = self.precision + series_guard_digits + pow_guard_digits;
419 let context = Context::<R>::new(work_precision);
420 let x = FBig::new(context.repr_round_ref(x).value(), context);
421 let logb = context.ln_base::<B>(reborrow_cache(&mut cache));
422 let (s, r) = x.div_rem_euclid(logb);
423
424 let s: isize = match s.try_into() {
425 Ok(v) => v,
426 Err(_) => {
427 // |floor(x / ln B)| overflows isize — x is astronomically large, so the
428 // result is an infinity (x → +∞) or underflows to the limit (x → −∞).
429 //
430 // TODO: this branch discards the rounding mode. For a huge
431 // *negative* x the true exp(x) is positive but below the exponent range, so
432 // directed Up/Away should return the minimum positive value (and exp_m1 the
433 // value just above -1) rather than +0 / Exact(-1).
434 return if input_sign == Sign::Positive {
435 Err(FpError::Overflow(Sign::Positive))
436 } else if minus_one {
437 Ok(Exact(-FBig::ONE)) // exp_m1(−∞) = −1 (finite)
438 } else {
439 Err(FpError::Underflow(Sign::Positive)) // exp(−∞) = +0
440 };
441 }
442 };
443 (s, n, r)
444 };
445
446 let r = r >> n as isize;
447 let mut factorial = IBig::ONE;
448 let mut pow = r.clone();
449 let mut sum = if no_scaling {
450 r.clone()
451 } else {
452 FBig::ONE + &r
453 };
454
455 let mut k = 2;
456 loop {
457 factorial *= k;
458 pow *= &r;
459
460 let increase = &pow / &factorial;
461 if increase.abs_cmp(&sum.sub_ulp()).is_le() {
462 break;
463 }
464 sum += increase;
465 k += 1;
466 }
467
468 if no_scaling {
469 Ok(sum.with_precision(self.precision))
470 } else if minus_one {
471 // Power at the series' working precision (it already carries the 2n guard
472 // digits that the Bⁿ powering amplifies away). The final "−1" can cancel at
473 // most ~1 leading digit here (the |x| < 1/B case is handled by no_scaling),
474 // which the same guard digits comfortably absorb.
475 let pow_ctx = Context::<R>::new(work_precision);
476 let v = pow_ctx.unwrap_fp(pow_ctx.powi(sum.repr(), Repr::<B>::BASE.pow(n).into()));
477 Ok(((v << s) - FBig::ONE).with_precision(self.precision))
478 } else {
479 let pow_ctx = Context::<R>::new(work_precision);
480 let v = pow_ctx.unwrap_fp(pow_ctx.powi(sum.repr(), Repr::<B>::BASE.pow(n).into()));
481 Ok((v << s).with_precision(self.precision))
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::round::mode;
490
491 #[test]
492 fn test_exp_overflow_is_infinity() {
493 let ctx = Context::<mode::HalfEven>::new(53);
494 // exp(huge) overflows the isize exponent range -> Overflow at Context level.
495 // Need x large enough that floor(x/ln2) > isize::MAX, i.e. x > ~2^62.5.
496 let huge = Repr::new(IBig::from(1) << 63, 0);
497 assert_eq!(ctx.exp::<2>(&huge, None), Err(FpError::Overflow(Sign::Positive)));
498
499 // exp(huge negative) underflows to +0
500 let neg = Repr::new(-(IBig::from(1) << 63), 0);
501 assert_eq!(ctx.exp::<2>(&neg, None), Err(FpError::Underflow(Sign::Positive)));
502
503 // exp_m1(huge negative) -> -1 (a finite value, not an error)
504 let m1 = ctx.exp_m1::<2>(&neg, None).unwrap().value();
505 assert_eq!(m1, -FBig::<mode::HalfEven>::ONE);
506 }
507
508 // A sharp OOM regression needs an exponent gap large enough that 2^gap exceeds any
509 // memory (gap ≳ 1e11), yet with floor(x/ln2) still fitting isize so the overflow
510 // branch is not taken. That window only exists where isize is 64-bit: on 32-bit,
511 // isize tops out at ~2.1e9 — below any OOM-inducing gap — so the overflow branch
512 // always intervenes first. The fix itself (log2_bounds in round_fract) is
513 // arch-independent; only this dedicated sharp test is 64-bit-only.
514 #[cfg(target_pointer_width = "64")]
515 #[test]
516 fn test_exp_m1_large_negative_no_oom() {
517 // Regression test: exp_m1 of a large-magnitude negative input subtracts
518 // 1 from an astronomically small exp(x), so the aligned subtraction hands
519 // round_fract a sparse sticky tail whose `precision` equals the exponent gap
520 // (~6.6e18 here). The debug assert in round_fract used to materialize B^precision
521 // (2^6.6e18 bits → certain OOM); it now uses log2 bounds and must not allocate.
522 //
523 // x = -2^62: the gap (~6.6e18) dwarfs memory, yet floor(x/ln2) ≈ -6.6e18 still
524 // fits 64-bit isize (≈9.2e18), so this does NOT enter the overflow branch.
525 let ctx = Context::<mode::Up>::new(2);
526 let x = Repr::new(-(IBig::from(1) << 62), 0);
527 let r = ctx.exp_m1::<2>(&x, None).unwrap().value();
528 // exp_m1(-2^62) = -1 + ε (ε > 0 vanishingly small); under Up it rounds above -1.
529 assert!(
530 r > -FBig::<mode::Up, 2>::ONE,
531 "exp_m1(-2^62) under Up should round above -1, got {r:?}"
532 );
533 }
534
535 #[test]
536 fn test_exact_results_on_unlimited_precision() {
537 // Regression test: values carrying precision 0 (unlimited) — produced by
538 // `try_from(0.0)` and the `FBig::ONE`/`ZERO` constants — must still compute
539 // their exact-result special cases instead of panicking in
540 // assert_limited_precision before reaching the shortcut.
541 type F = FBig<mode::HalfEven, 2>;
542
543 let zero = F::try_from(0.0_f64).unwrap();
544 assert_eq!(zero.exp(), F::ONE);
545 assert_eq!(zero.exp_m1(), F::ZERO);
546 assert_eq!(zero.sqrt(), F::ZERO);
547 assert_eq!(zero.ln_1p(), F::ZERO);
548
549 // -0.0 preserves its sign through exp_m1 and sqrt.
550 let neg_zero = F::try_from(-0.0_f64).unwrap();
551 assert!(neg_zero.exp_m1().repr().is_neg_zero());
552 assert!(neg_zero.sqrt().repr().is_neg_zero());
553
554 // FBig::ONE carries unlimited precision; ln(1) = 0 is exact.
555 assert_eq!(F::ONE.ln(), F::ZERO);
556 }
557
558 #[test]
559 fn test_powf_zero_base() {
560 use crate::DBig;
561 // powf with a float exponent returns the *positive* result on a zero base
562 // (matching the common float-pow convention); use powi for the signed result.
563 let ctx = Context::<mode::HalfEven>::new(53);
564 // powf(-0, 2.0) = +0 (NOT -0)
565 let r = ctx
566 .powf::<2>(&Repr::<2>::neg_zero(), &Repr::new(2.into(), 0), None)
567 .unwrap()
568 .value();
569 assert!(r.repr().is_pos_zero(), "expected +0");
570 assert!(!r.repr().is_neg_zero(), "powf(-0, x) should be +0, not -0");
571 // powf(0, -1) = +inf
572 let r = ctx
573 .powf::<2>(&Repr::<2>::zero(), &Repr::new((-1i32).into(), 0), None)
574 .unwrap()
575 .value();
576 assert!(r.repr().is_infinite());
577 assert_eq!(r.repr().sign(), Sign::Positive);
578 // powi(-0, 3) = -0 (the sign-correct, integer-exponent variant)
579 let r = ctx
580 .powi::<2>(&Repr::<2>::neg_zero(), 3.into())
581 .unwrap()
582 .value();
583 assert!(r.repr().is_neg_zero());
584 let _ = DBig::ZERO;
585 }
586}