Skip to main content

dashu_float/
log.rs

1use dashu_base::{
2    utils::{next_down, next_up},
3    AbsOrd,
4    Approximation::*,
5    EstimatedLog2, PowerOfTwo, Sign, UnsignedAbs,
6};
7use dashu_int::IBig;
8
9use crate::{
10    error::{assert_finite, assert_limited_precision, FpError, FpResult},
11    fbig::FBig,
12    math::cache::{reborrow_cache, ConstCache},
13    math::trig::series_radius,
14    repr::{Context, Repr, Word},
15    round::{mode, ErrorBounds, Round, Rounded},
16};
17use core::cmp::Ordering;
18
19impl<const B: Word> EstimatedLog2 for Repr<B> {
20    // currently a Word has at most 64 bits, so log2() < f32::MAX
21    fn log2_bounds(&self) -> (f32, f32) {
22        if self.significand.is_zero() {
23            return (f32::NEG_INFINITY, f32::NEG_INFINITY);
24        }
25
26        // log(s*B^e) = log(s) + e*log(B)
27        let (logs_lb, logs_ub) = self.significand.log2_bounds();
28        let (logb_lb, logb_ub) = if B.is_power_of_two() {
29            let log = B.trailing_zeros() as f32;
30            (log, log)
31        } else {
32            B.log2_bounds()
33        };
34        let e = self.exponent as f32;
35        let (lb, ub) = if self.exponent >= 0 {
36            (logs_lb + e * logb_lb, logs_ub + e * logb_ub)
37        } else {
38            (logs_lb + e * logb_ub, logs_ub + e * logb_lb)
39        };
40        (next_down(lb), next_up(ub))
41    }
42
43    fn log2_est(&self) -> f32 {
44        let logs = self.significand.log2_est();
45        let logb = if B.is_power_of_two() {
46            B.trailing_zeros() as f32
47        } else {
48            B.log2_est()
49        };
50        logs + self.exponent as f32 * logb
51    }
52}
53
54impl<R: Round, const B: Word> EstimatedLog2 for FBig<R, B> {
55    #[inline]
56    fn log2_bounds(&self) -> (f32, f32) {
57        self.repr.log2_bounds()
58    }
59
60    #[inline]
61    fn log2_est(&self) -> f32 {
62        self.repr.log2_est()
63    }
64}
65
66impl<R: ErrorBounds, const B: Word> FBig<R, B> {
67    /// Calculate the natural logarithm function (`log(x)`) on the float number.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// # use core::str::FromStr;
73    /// # use dashu_base::ParseError;
74    /// # use dashu_float::DBig;
75    /// let a = DBig::from_str("1.234")?;
76    /// assert_eq!(a.ln(), DBig::from_str("0.2103")?);
77    /// # Ok::<(), ParseError>(())
78    /// ```
79    #[inline]
80    pub fn ln(&self) -> Self {
81        self.context.unwrap_fp(self.context.ln(&self.repr, None))
82    }
83
84    /// Calculate the natural logarithm function (`log(x+1)`) on the float number
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// # use core::str::FromStr;
90    /// # use dashu_base::ParseError;
91    /// # use dashu_float::DBig;
92    /// let a = DBig::from_str("0.1234")?;
93    /// assert_eq!(a.ln_1p(), DBig::from_str("0.11636")?);
94    /// # Ok::<(), ParseError>(())
95    /// ```
96    #[inline]
97    pub fn ln_1p(&self) -> Self {
98        self.context.unwrap_fp(self.context.ln_1p(&self.repr, None))
99    }
100
101    /// Calculate the base-2 logarithm (`log2(x)`) on the float number.
102    ///
103    /// Correctly rounded to the context's precision under any rounding mode. For an exact power
104    /// of two the result is the exact integer `log2(x)`.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// # use core::str::FromStr;
110    /// # use dashu_base::ParseError;
111    /// # use dashu_float::DBig;
112    /// let a = DBig::from_str("8")?;
113    /// assert_eq!(a.log2(), DBig::from_str("3")?);
114    /// # Ok::<(), ParseError>(())
115    /// ```
116    #[inline]
117    pub fn log2(&self) -> Self {
118        self.context.unwrap_fp(self.context.log2(&self.repr, None))
119    }
120}
121
122// `ln2`/`ln10`/`iacoth`/`ln_base`/`ln_compute` are the near-correct logarithm primitives: they
123// evaluate the series at a working precision and round once, without a Ziv certification step.
124// They live on `R: Round` so that base conversion (`with_base_and_precision`, which only needs a
125// near-correct constant `ln(B)`) can use them without inheriting the `ErrorBounds` bound. The
126// correctly-rounded public `ln`/`ln_1p` (in the `ErrorBounds` impl below) wrap `ln_compute` in a
127// Ziv loop.
128impl<R: Round> Context<R> {
129    /// Calculate log(2)
130    ///
131    /// The precision of the output will be larger than self.precision
132    #[inline]
133    fn ln2<const B: Word>(&self, cache: Option<&mut ConstCache>) -> FBig<R, B> {
134        if let Some(c) = cache {
135            return c.ln2::<B, R>(self.precision);
136        }
137        // log(2) = 4L(6) + 2L(99)
138        // see formula (24) from Gourdon, Xavier, and Pascal Sebah.
139        // "The Logarithmic Constant: Log 2." (2004)
140        4 * self.iacoth(6.into()) + 2 * self.iacoth(99.into())
141    }
142
143    /// Calculate log(10)
144    ///
145    /// The precision of the output will be larger than self.precision
146    #[inline]
147    fn ln10<const B: Word>(&self, cache: Option<&mut ConstCache>) -> FBig<R, B> {
148        if let Some(c) = cache {
149            return c.ln10::<B, R>(self.precision);
150        }
151        // log(10) = log(2) + log(5) = 3log(2) + 2L(9)
152        3 * self.ln2(None) + 2 * self.iacoth(9.into())
153    }
154
155    /// Calculate log(B), for internal use only
156    ///
157    /// The precision of the output will be larger than self.precision
158    #[inline]
159    pub(crate) fn ln_base<const B: Word>(&self, cache: Option<&mut ConstCache>) -> FBig<R, B> {
160        if let Some(c) = cache {
161            return c.ln_base::<B, R>(self.precision);
162        }
163        match B {
164            2 => self.ln2(None),
165            10 => self.ln10(None),
166            i if i.is_power_of_two() => self.ln2(None) * i.trailing_zeros(),
167            _ => {
168                // Near-correct ln(B) via the atanh series (no Ziv certification — base conversion
169                // only needs a near-correct constant). `ln_compute` is on `R: Round`, so this keeps
170                // `ln_base` callable from `R: Round` contexts (base conversion).
171                let guard = self.base_guard_digits::<B>() + 2;
172                self.ln_compute::<B>(
173                    &Repr::new(Repr::<B>::BASE.into(), 0),
174                    self.precision + guard,
175                    false,
176                    None,
177                )
178                .0
179            }
180        }
181    }
182
183    /// Calculate L(n) = acoth(n) = atanh(1/n) = 1/2 log((n+1)/(n-1)), given by the
184    /// series
185    ///
186    /// ```text
187    ///                1     n + 1              1
188    ///   atanh(1/n) = — log(—————) = Σ   ——————————————————
189    ///                2     n - 1   i≥0 n^(2i+1) · (2i+1)
190    /// ```
191    ///
192    /// This method is intended to be used in logarithm calculation,
193    /// so the precision of the output will be larger than desired precision.
194    ///
195    /// Evaluated by binary splitting (see [`iacoth_bs`][crate::math::cache::iacoth_bs]):
196    /// the exact integer tree state `(P, Q, T)` over `[1, N)` satisfies
197    /// `L(n) = (Q + T)/(n·Q)`, with `Q` kept at O(p) digits by the ratio-form
198    /// term recurrence.
199    fn iacoth<const B: Word>(&self, n: IBig) -> FBig<R, B> {
200        let n: u32 = (&n).try_into().expect("iacoth argument must fit in u32");
201
202        // number of series terms until r_k < B^{-p}:  (2k+1)·log_B(n) > p.
203        // The count is generously over-provisioned, so a truncating cast stands in
204        // for a ceiling.
205        let log_b_n = n.log2_est() / B.log2_est();
206        let num_terms = (self.precision as f32 / (2.0 * log_b_n)) as usize + 10;
207
208        let (_p, q, t) = crate::math::cache::iacoth_bs(n, 1, num_terms + 1);
209
210        // L(n) = (Q + T) / (n·Q). Extra guard digits absorb the division's rounding
211        // (the binary-splitting state is exact, so only this single round loses anything).
212        let guard_digits = self.base_guard_digits::<B>();
213        let work_context = Self::new(self.precision + guard_digits + 2);
214
215        let num = work_context.convert_int::<B>(q.as_ibig() + &t).value();
216        let denom = work_context.convert_int::<B>(IBig::from(n) * &q).value();
217        num / denom
218    }
219
220    /// Evaluate `ln(x)` (or `ln(x+1)` when `one_plus`) at `work_precision` via the atanh series,
221    /// returning `(value, error_radius)`.
222    ///
223    /// This is the near-correct computation core shared by the public Ziv-backed `ln`/`ln_1p`
224    /// (which wrap it in a retry loop) and by `ln_base` (which only needs a near-correct constant
225    /// `ln(B)`). It lives on `R: Round` so those near-correct callers don't inherit the
226    /// `ErrorBounds` bound. The radius is a provable upper bound on `|value − true|`, derived from
227    /// the term count (every series step is correctly rounded; the truncated tail is `< 1 ulp` by
228    /// the break test).
229    pub(crate) fn ln_compute<const B: Word>(
230        &self,
231        x: &Repr<B>,
232        mut work_precision: usize,
233        one_plus: bool,
234        mut cache: Option<&mut ConstCache>,
235    ) -> (FBig<R, B>, FBig<R, B>) {
236        // log(x) = log(x·B⁻ˢ) + s·log(B), with s = floor(log_B(x)) so x·B⁻ˢ ∈ [1, B).
237        let context = Context::<R>::new(work_precision);
238        let x = FBig::new(context.repr_round_ref(x).value(), context);
239
240        // When one_plus is true and |x| < 1/B, the input is fed into the Maclaurin without scaling
241        let no_scaling = one_plus && x.log2_est() < -B.log2_est();
242
243        let (s, mut x_scaled) = if no_scaling {
244            (0, x)
245        } else {
246            let x = if one_plus { x + FBig::ONE } else { x };
247
248            let log2 = x.log2_bounds().0;
249            let s = log2 as isize - (log2 < 0.) as isize; // floor(log2(x))
250
251            let x_scaled = if B == 2 {
252                x >> s
253            } else if s > 0 {
254                x / (IBig::ONE << s as usize)
255            } else {
256                x * (IBig::ONE << (-s) as usize)
257            };
258            debug_assert!(x_scaled >= FBig::<R, B>::ONE);
259            (s, x_scaled)
260        };
261
262        if s < 0 || x_scaled.repr.sign() == Sign::Negative {
263            // when s or x_scaled is negative, the final addition is actually a subtraction,
264            // therefore we need to double the precision to get the correct result
265            work_precision += self.precision;
266            x_scaled.context.precision = work_precision;
267        }
268        let work_context = Context::new(work_precision);
269
270        // after the number is scaled to nearly one, use Maclaurin series on log(x) = 2atanh(z):
271        // let z = (x-1)/(x+1) < 1, log(x) = 2atanh(z) = 2Σ(z²ⁱ⁺¹/(2i+1)) for i = 1,3,5,...
272        let z = if no_scaling {
273            let d = &x_scaled + (FBig::ONE + FBig::ONE);
274            x_scaled / d
275        } else {
276            (&x_scaled - FBig::ONE) / (x_scaled + FBig::ONE)
277        };
278        let z2 = z.sqr();
279        let mut pow = z.clone();
280        let mut sum = z;
281        let mut terms: usize = 1; // the leading z term
282
283        let mut k: usize = 3;
284        loop {
285            pow *= &z2;
286
287            let increase = &pow / work_context.convert_int::<B>(k.into()).value();
288            if increase.abs_cmp(&sum.ulp_lb()).is_le() {
289                break;
290            }
291
292            sum += increase;
293            k += 2;
294            terms += 1;
295        }
296
297        // compose the logarithm of the original number
298        let result: FBig<R, B> = if no_scaling {
299            2 * sum.clone()
300        } else {
301            2 * sum.clone() + (s * work_context.ln2::<B>(reborrow_cache(&mut cache)))
302        };
303
304        // Provable error radius, expressed in `result`-ULPs (not `sum`-ULPs). Each series step
305        // rounds once (< 1 ULP of the running sum) and the truncated tail is < 1 ULP by the break
306        // test, so |sum − true| < (terms + 2)·ulp(sum); result = 2·sum + s·ln2 amplifies by ~2 and
307        // adds a few reconstruction ULPs. Since result ≈ 2·sum, ulp(result) ≈ 2·ulp(sum), giving
308        // |result − true| < (terms + 2)·ulp(result) + overhead — we carry a generous margin.
309        //
310        // Basing the radius on `result.ulp()` (not `sum.ulp()`) keeps its exponent aligned with
311        // `a` (= result) in the Ziv containment test, so `a − e` avoids a slow exponent-misaligned
312        // unlimited-precision subtract — a ~3× speedup on `ln` at high precision.
313        let radius = series_radius(&result, terms);
314        (result, radius)
315    }
316}
317
318// `ln`/`ln_1p` are correctly rounded via the Ziv loop, whose containment test needs the rounding
319// preimage (`R: ErrorBounds`). They delegate the series to `ln_compute`.
320impl<R: ErrorBounds> Context<R> {
321    /// Calculate the natural logarithm function (`log(x)`) on the float number under this context.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// # use core::str::FromStr;
327    /// # use dashu_base::ParseError;
328    /// # use dashu_float::DBig;
329    /// use dashu_base::Approximation::*;
330    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
331    ///
332    /// let context = Context::<HalfAway>::new(2);
333    /// let a = DBig::from_str("1.234")?;
334    /// assert_eq!(context.ln(&a.repr(), None), Ok(Inexact(DBig::from_str("0.21")?, NoOp)));
335    /// # Ok::<(), ParseError>(())
336    /// ```
337    #[inline]
338    pub fn ln<const B: Word>(
339        &self,
340        x: &Repr<B>,
341        cache: Option<&mut ConstCache>,
342    ) -> FpResult<FBig<R, B>> {
343        if x.is_infinite() {
344            return Err(FpError::InfiniteInput);
345        }
346        if x.significand.is_zero() {
347            // ln(±0) = -inf (a value, not an error)
348            return Ok(Exact(FBig::new(Repr::neg_infinity(), *self)));
349        }
350        if x.sign() == Sign::Negative {
351            return Err(FpError::OutOfDomain);
352        }
353        Ok(self.ln_internal(x, false, cache))
354    }
355
356    /// Calculate the natural logarithm function (`log(x+1)`) on the float number under this context.
357    ///
358    /// # Examples
359    ///
360    /// ```
361    /// # use core::str::FromStr;
362    /// # use dashu_base::ParseError;
363    /// # use dashu_float::DBig;
364    /// use dashu_base::Approximation::*;
365    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
366    ///
367    /// let context = Context::<HalfAway>::new(2);
368    /// let a = DBig::from_str("0.1234")?;
369    /// assert_eq!(context.ln_1p(&a.repr(), None), Ok(Inexact(DBig::from_str("0.12")?, AddOne)));
370    /// # Ok::<(), ParseError>(())
371    /// ```
372    #[inline]
373    pub fn ln_1p<const B: Word>(
374        &self,
375        x: &Repr<B>,
376        cache: Option<&mut ConstCache>,
377    ) -> FpResult<FBig<R, B>> {
378        if x.is_infinite() {
379            return Err(FpError::InfiniteInput);
380        }
381        // Domain of ln_1p is x > -1. x == -1 gives -inf; x < -1 is out of domain.
382        if x.sign() == Sign::Negative && !x.significand.is_zero() {
383            match FBig::<R, B>::new(x.clone(), *self).abs_cmp(&FBig::ONE) {
384                Ordering::Greater => return Err(FpError::OutOfDomain), // x < -1
385                Ordering::Equal => return Ok(Exact(FBig::new(Repr::neg_infinity(), *self))),
386                _ => {}
387            }
388        }
389        Ok(self.ln_internal(x, true, cache))
390    }
391
392    fn ln_internal<const B: Word>(
393        &self,
394        x: &Repr<B>,
395        one_plus: bool,
396        mut cache: Option<&mut ConstCache>,
397    ) -> Rounded<FBig<R, B>> {
398        assert_finite(x);
399
400        // Exact special cases first: they need no rounding, so a precision-0 (unlimited)
401        // value such as `FBig::ONE` or the one from `try_from(0.0)` must still resolve
402        // ln/ln_1p exactly rather than tripping the limited-precision assertion below.
403        if !one_plus && x.is_one() {
404            return Exact(FBig::ZERO); // ln(1) = +0
405        }
406        if one_plus && x.significand.is_zero() {
407            // ln_1p(±0) = ±0
408            let zero = if x.is_neg_zero() {
409                FBig::new(Repr::neg_zero(), *self)
410            } else {
411                FBig::ZERO
412            };
413            return Exact(zero);
414        }
415
416        assert_limited_precision(self.precision);
417
418        // Correct rounding via the Ziv loop: `ln_compute` evaluates the atanh series at `p + guard`
419        // and reports a provable error radius; the driver retries with more guard digits until the
420        // approximation's error interval lies entirely inside one rounding bin. The guard is a
421        // *performance* knob (first-attempt hit rate), not a correctness backstop — Ziv certifies
422        // the result. (The pre-Ziv `+ 2` is retained: with the conservative radius below it is still
423        // needed for the first attempt to clear the half-ulp preimage at typical precisions.)
424        let base_guard = self.base_guard_digits::<B>() + 2;
425        self.ziv(base_guard + one_plus as usize, |guard| {
426            self.ln_compute::<B>(x, self.precision + guard, one_plus, reborrow_cache(&mut cache))
427        })
428    }
429
430    /// Calculate the base-2 logarithm (`log2(x)`) on the float number under this context.
431    ///
432    /// Correctly rounded to the context's precision under any rounding mode; for an exact power
433    /// of two the result is the exact integer `log2(x)`.
434    ///
435    /// # Domain
436    ///
437    /// `log2(±0) = −∞` and a negative (non-zero) input is out of domain; an infinite input is an
438    /// error (a finite context cannot produce the infinite `log2(+∞) = +∞` exactly).
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// # use core::str::FromStr;
444    /// # use dashu_base::ParseError;
445    /// # use dashu_float::DBig;
446    /// use dashu_base::Approximation::*;
447    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
448    ///
449    /// let context = Context::<HalfAway>::new(4);
450    /// let a = DBig::from_str("10")?;
451    /// assert_eq!(context.log2(&a.repr(), None), Ok(Inexact(DBig::from_str("3.322")?, AddOne)));
452    /// # Ok::<(), ParseError>(())
453    /// ```
454    #[inline]
455    pub fn log2<const B: Word>(
456        &self,
457        x: &Repr<B>,
458        cache: Option<&mut ConstCache>,
459    ) -> FpResult<FBig<R, B>> {
460        if x.is_infinite() {
461            return Err(FpError::InfiniteInput);
462        }
463        if x.significand.is_zero() {
464            // log2(±0) = -inf (a value, not an error)
465            return Ok(Exact(FBig::new(Repr::neg_infinity(), *self)));
466        }
467        if x.sign() == Sign::Negative {
468            return Err(FpError::OutOfDomain);
469        }
470        Ok(self.log2_internal(x, cache))
471    }
472
473    fn log2_internal<const B: Word>(
474        &self,
475        x: &Repr<B>,
476        mut cache: Option<&mut ConstCache>,
477    ) -> Rounded<FBig<R, B>> {
478        assert_finite(x);
479
480        // Exact shortcuts first — they also cover unlimited precision, which the Ziv loop below
481        // rejects via its limited-precision assertion.
482        if x.is_one() {
483            return Exact(FBig::ZERO); // log2(1) = +0
484        }
485
486        // Exact power-of-two shortcut: if x = 2^k for an integer k, log2(x) = k. This is *required*
487        // for directed rounding — the Ziv loop below cannot certify an exactly-representable
488        // result whose true value sits on a rounding boundary (its shrinking error interval
489        // always straddles the boundary), so without this shortcut log2(2^-159) under `Up` would
490        // exhaust the retry cap and return k + 1 ulp instead of the exact k.
491        //
492        // log2(x) = log2(significand) + exponent·log2(B). With significand = 2^m this is an exact
493        // integer whenever log2(B) is integral (B a power of two), or — for a non-power-of-two
494        // base — when the exponent is zero.
495        let mag = (&x.significand).unsigned_abs();
496        if mag.is_power_of_two() && (x.exponent == 0 || B.is_power_of_two()) {
497            let m = mag.trailing_zeros().unwrap(); // = log2(significand)
498            let log2_b = B.trailing_zeros() as isize;
499            let k = IBig::from(m) + IBig::from(x.exponent) * IBig::from(log2_b);
500            return self.convert_int::<B>(k);
501        }
502
503        assert_limited_precision(self.precision);
504
505        // log2(x) = ln(x)/ln(2), correctly rounded via the Ziv loop. Rounding ln(x) and ln(2)
506        // separately and dividing once is only *near*-correct: under directed rounding, rounding
507        // both operands toward the mode does not bound the quotient (enlarging a positive
508        // denominator shrinks it). Instead each `ln_compute` reports a provable error radius, and
509        // the two radii are carried through the division as an outward-rounded interval [lo, hi]
510        // that is guaranteed to contain the true log2(x); the driver certifies once that interval
511        // lies inside a single rounding bin.
512        let initial_guard = self.base_guard_digits::<B>() + 4;
513        self.ziv(initial_guard, |guard| {
514            let work_precision = self.precision + guard;
515            let (lx, ex) =
516                self.ln_compute::<B>(x, work_precision, false, reborrow_cache(&mut cache));
517            // ln(2) via the same near-correct primitive so it carries a provable radius too.
518            let two = Repr::new(IBig::from(2), 0);
519            let (l2, e2) =
520                self.ln_compute::<B>(&two, work_precision, false, reborrow_cache(&mut cache));
521
522            // True ln(x) ∈ [lx−ex, lx+ex] and true ln(2) ∈ [l2−e2, l2+e2] ⊆ (0, ∞). With a
523            // positive denominator the quotient ln(x)/ln(2) is minimized by the low numerator
524            // over the high denominator and maximized by the converse. Directing each endpoint's
525            // rounding outward (lo down, hi up) keeps [lo, hi] a true containing interval.
526            let down = Context::<mode::Down>::new(work_precision);
527            let up = Context::<mode::Up>::new(work_precision);
528            let nx_lo = down.sub(&lx.repr, &ex.repr).unwrap().value();
529            let nx_hi = up.add(&lx.repr, &ex.repr).unwrap().value();
530            let d_lo = down.sub(&l2.repr, &e2.repr).unwrap().value();
531            let d_hi = up.add(&l2.repr, &e2.repr).unwrap().value();
532            debug_assert!(
533                d_lo.repr.sign() == Sign::Positive,
534                "ln(2) lower bound must stay positive (guard digits keep e2 ≪ ln 2 ≈ 0.693)"
535            );
536            let lo = down.div(&nx_lo.repr, &d_hi.repr).unwrap().value();
537            let hi = up.div(&nx_hi.repr, &d_lo.repr).unwrap().value();
538
539            // Working-precision estimate; the driver re-rounds it to the target precision, so the
540            // mode used here is immaterial to correctness.
541            let value = Context::<R>::new(work_precision)
542                .div(&lx.repr, &l2.repr)
543                .unwrap()
544                .value();
545
546            // Radius: a provable bound on |value − true|. The true value lies in [lo, hi], and
547            // `value` is within one working ulp of lx/l2 ∈ [lo, hi], so |value − true| ≤
548            // (hi − lo) + ulp_w. Computed at unlimited precision so the bound arithmetic is exact
549            // (no rounding that could under-report it), yet scaled by the working-precision span
550            // and ulp so it shrinks as the guard grows and the loop converges. `lo`/`hi` were
551            // rounded under Down/Up; their *values* are mode-independent, so rebuild them in the
552            // target mode R via their reprs to keep the arithmetic single-mode.
553            let unlim = Context::<R>::new(0);
554            let span = FBig::new(hi.repr.clone(), unlim) - FBig::new(lo.repr.clone(), unlim);
555            let ulp_w = value.ulp().with_precision(0).value();
556            let radius = span + ulp_w;
557            (value, radius)
558        })
559    }
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565    use crate::round::mode;
566
567    #[test]
568    fn test_ln_zero_is_neg_infinity() {
569        let ctx = Context::<mode::HalfEven>::new(53);
570        let r = ctx.ln::<2>(&Repr::<2>::zero(), None).unwrap().value();
571        assert!(r.repr().is_infinite());
572        assert_eq!(r.repr().sign(), Sign::Negative);
573    }
574
575    #[test]
576    fn test_iacoth() {
577        let context = Context::<mode::Zero>::new(10);
578        let binary_6 = context.iacoth::<2>(6.into()).with_precision(10).value();
579        assert_eq!(binary_6.repr.significand, IBig::from(689));
580        let decimal_6 = context.iacoth::<10>(6.into()).with_precision(10).value();
581        assert_eq!(decimal_6.repr.significand, IBig::from(1682361183));
582
583        let context = Context::<mode::Zero>::new(40);
584        let decimal_6 = context.iacoth::<10>(6.into()).with_precision(40).value();
585        assert_eq!(
586            decimal_6.repr.significand,
587            IBig::from_str_radix("1682361183106064652522967051084960450557", 10).unwrap()
588        );
589
590        let context = Context::<mode::Zero>::new(201);
591        let binary_6 = context.iacoth::<2>(6.into()).with_precision(201).value();
592        assert_eq!(
593            binary_6.repr.significand,
594            IBig::from_str_radix(
595                "2162760151454160450909229890833066944953539957685348083415205",
596                10
597            )
598            .unwrap()
599        );
600    }
601
602    #[test]
603    fn test_ln2_ln10() {
604        let context = Context::<mode::Zero>::new(45);
605        let decimal_ln2 = context.ln2::<10>(None).with_precision(45).value();
606        assert_eq!(
607            decimal_ln2.repr.significand,
608            IBig::from_str_radix("693147180559945309417232121458176568075500134", 10).unwrap()
609        );
610        let decimal_ln10 = context.ln10::<10>(None).with_precision(45).value();
611        assert_eq!(
612            decimal_ln10.repr.significand,
613            IBig::from_str_radix("230258509299404568401799145468436420760110148", 10).unwrap()
614        );
615
616        let context = Context::<mode::Zero>::new(180);
617        let binary_ln2 = context.ln2::<2>(None).with_precision(180).value();
618        assert_eq!(
619            binary_ln2.repr.significand,
620            IBig::from_str_radix("1062244963371879310175186301324412638028404515790072203", 10)
621                .unwrap()
622        );
623        let binary_ln10 = context.ln10::<2>(None).with_precision(180).value();
624        assert_eq!(
625            binary_ln10.repr.significand,
626            IBig::from_str_radix("882175346869410758689845931257775553286341791676474847", 10)
627                .unwrap()
628        );
629    }
630
631    #[test]
632    fn test_log2_domain() {
633        let ctx = Context::<mode::HalfEven>::new(53);
634        // log2(±0) = -inf (a value, not an error)
635        let r = ctx.log2::<2>(&Repr::<2>::zero(), None).unwrap().value();
636        assert!(r.repr.is_infinite());
637        assert_eq!(r.repr.sign(), Sign::Negative);
638        // log2(negative) is out of domain
639        assert!(matches!(
640            ctx.log2::<2>(&Repr::new((-1).into(), 0), None),
641            Err(FpError::OutOfDomain)
642        ));
643        // an infinite input is rejected
644        assert!(matches!(ctx.log2::<2>(&Repr::infinity(), None), Err(FpError::InfiniteInput)));
645    }
646
647    #[test]
648    fn test_log2_exact_power_of_two() {
649        // log2(2^k) = k exactly under every rounding mode. Regression for the directed-rounding
650        // defect: rounding ln(x) and ln(2) each toward the mode and dividing once does not bound
651        // the quotient, so previously log2(2^-159) under `Up` returned -159 + 1 ulp.
652        let p = 53;
653        for k in [0isize, 1, -1, 5, 159, -159, 1000, -1000] {
654            let x = Repr::<2>::new(IBig::from(1), k); // 2^k
655            let r_down = Context::<mode::Down>::new(p)
656                .log2::<2>(&x, None)
657                .unwrap()
658                .value();
659            let r_up = Context::<mode::Up>::new(p)
660                .log2::<2>(&x, None)
661                .unwrap()
662                .value();
663            let r_zero = Context::<mode::Zero>::new(p)
664                .log2::<2>(&x, None)
665                .unwrap()
666                .value();
667            let r_he = Context::<mode::HalfEven>::new(p)
668                .log2::<2>(&x, None)
669                .unwrap()
670                .value();
671            // Every directed mode produces the identical value — no mode-dependent ulp.
672            assert_eq!(r_down.repr, r_he.repr, "Down != HalfEven for log2(2^{k})");
673            assert_eq!(r_up.repr, r_he.repr, "Up != HalfEven for log2(2^{k})");
674            assert_eq!(r_zero.repr, r_he.repr, "Zero != HalfEven for log2(2^{k})");
675            // And that value is exactly k.
676            assert_eq!(r_he.to_int().value(), IBig::from(k), "value for log2(2^{k})");
677        }
678    }
679
680    #[test]
681    fn test_log2_exact_power_of_two_decimal_base() {
682        // In a non-power-of-two base the shortcut still fires when the exponent is zero: a
683        // significand that is itself a power of two makes x = 2^m exactly.
684        let p = 53;
685        for (sig, want) in [(8i32, 3isize), (1024, 10), (2, 1), (32, 5)] {
686            let x = Repr::<10>::new(IBig::from(sig), 0);
687            let r_down = Context::<mode::Down>::new(p)
688                .log2::<10>(&x, None)
689                .unwrap()
690                .value();
691            let r_up = Context::<mode::Up>::new(p)
692                .log2::<10>(&x, None)
693                .unwrap()
694                .value();
695            let r_he = Context::<mode::HalfEven>::new(p)
696                .log2::<10>(&x, None)
697                .unwrap()
698                .value();
699            assert_eq!(r_down.repr, r_he.repr, "Down != HalfEven for log2({sig}) base 10");
700            assert_eq!(r_up.repr, r_he.repr, "Up != HalfEven for log2({sig}) base 10");
701            assert_eq!(r_he.to_int().value(), IBig::from(want), "value for log2({sig}) base 10");
702        }
703    }
704
705    /// For a non-power-of-two significand `sig` (so `log2` is irrational and never lands on a
706    /// rounding boundary), each directed result must equal a high-precision oracle rounded to the
707    /// target precision under the same mode — the definition of correct rounding.
708    fn check_log2_directed_matches_oracle<const B: Word>(sig: u32, p: usize) {
709        let oracle_ctx = Context::<mode::HalfEven>::new(p + 40);
710        let x = Repr::<B>::new(IBig::from(sig), 0);
711        let oracle = oracle_ctx.log2::<B>(&x, None).unwrap().value();
712
713        let want_down = Context::<mode::Down>::new(p)
714            .repr_round_ref(&oracle.repr)
715            .value();
716        let want_up = Context::<mode::Up>::new(p)
717            .repr_round_ref(&oracle.repr)
718            .value();
719        let want_he = Context::<mode::HalfEven>::new(p)
720            .repr_round_ref(&oracle.repr)
721            .value();
722
723        let got_down = Context::<mode::Down>::new(p)
724            .log2::<B>(&x, None)
725            .unwrap()
726            .value();
727        let got_up = Context::<mode::Up>::new(p)
728            .log2::<B>(&x, None)
729            .unwrap()
730            .value();
731        let got_he = Context::<mode::HalfEven>::new(p)
732            .log2::<B>(&x, None)
733            .unwrap()
734            .value();
735
736        assert_eq!(got_down.repr, want_down, "log2({sig}) base {B} under Down");
737        assert_eq!(got_up.repr, want_up, "log2({sig}) base {B} under Up");
738        assert_eq!(got_he.repr, want_he, "log2({sig}) base {B} under HalfEven");
739    }
740
741    #[test]
742    fn test_log2_directed_matches_oracle() {
743        let p = 24;
744        for sig in [3u32, 7, 10, 12345, 65537] {
745            check_log2_directed_matches_oracle::<2>(sig, p);
746        }
747        // Exercise a non-power-of-two base through the Ziv interval path too.
748        for sig in [3u32, 7, 10, 12345] {
749            check_log2_directed_matches_oracle::<10>(sig, p);
750        }
751    }
752}