Skip to main content

dashu_float/
log.rs

1use dashu_base::{
2    utils::{next_down, next_up},
3    Abs, AbsOrd,
4    Approximation::*,
5    EstimatedLog2, PowerOfTwo, Sign, UnsignedAbs,
6};
7use dashu_int::IBig;
8
9use crate::{
10    ball::{ceil_shift, Ball},
11    error::{assert_finite, assert_limited_precision, FpError, FpResult},
12    fbig::FBig,
13    math::cache::{reborrow_cache, ConstCache},
14    repr::{Context, Repr, Word},
15    round::{mode, ErrorBounds, Round},
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                .to_value_radius::<R>()
179                .0
180            }
181        }
182    }
183
184    /// `ln(B)` as a [`Ball`], carrying the mechanical radius.
185    ///
186    /// The cached bases (2, 10, powers of 2) evaluate correctly-rounded constants (error ≤ ½ ulp at
187    /// the working precision), so the fixed `8` ulps is a sound loose bound. A generic base falls
188    /// back to `ln_compute`'s atanh series, whose radius is ~(series terms + B) ulps — far larger
189    /// than 8 — so its mechanical radius is kept instead (a hard-coded `8` would under-bind the
190    /// `s·ln(B)` reconstruction error in `exp_compute`'s reduction).
191    pub(crate) fn ln_base_ball<const B: Word>(
192        &self,
193        mut cache: Option<&mut ConstCache>,
194    ) -> Ball<B> {
195        let ctx = Context::<mode::HalfEven>::new(self.precision);
196        match B {
197            10 => {
198                let logb = ctx.ln_base::<B>(reborrow_cache(&mut cache));
199                Ball::with_error(logb, IBig::from(8))
200            }
201            i if i.is_power_of_two() => {
202                let logb = ctx.ln_base::<B>(reborrow_cache(&mut cache));
203                Ball::with_error(logb, IBig::from(8))
204            }
205            _ => {
206                // Generic base: no cached sub-series applies, so compute ln(B) directly at the
207                // requested precision and keep `ln_compute`'s ball error.
208                ctx.ln_compute::<B>(
209                    &Repr::new(Repr::<B>::BASE.into(), 0),
210                    self.precision,
211                    false,
212                    reborrow_cache(&mut cache),
213                )
214            }
215        }
216    }
217
218    /// Calculate L(n) = acoth(n) = atanh(1/n) = 1/2 log((n+1)/(n-1)), given by the
219    /// series
220    ///
221    /// ```text
222    ///                1     n + 1              1
223    ///   atanh(1/n) = — log(—————) = Σ   ——————————————————
224    ///                2     n - 1   i≥0 n^(2i+1) · (2i+1)
225    /// ```
226    ///
227    /// This method is intended to be used in logarithm calculation,
228    /// so the precision of the output will be larger than desired precision.
229    ///
230    /// Evaluated by binary splitting (see [`iacoth_bs`][crate::math::cache::iacoth_bs]):
231    /// the exact integer tree state `(P, Q, T)` over `[1, N)` satisfies
232    /// `L(n) = (Q + T)/(n·Q)`, with `Q` kept at O(p) digits by the ratio-form
233    /// term recurrence.
234    fn iacoth<const B: Word>(&self, n: IBig) -> FBig<R, B> {
235        let n: u32 = (&n).try_into().expect("iacoth argument must fit in u32");
236
237        // number of series terms until r_k < B^{-p}:  (2k+1)·log_B(n) > p.
238        // The count is generously over-provisioned, so a truncating cast stands in
239        // for a ceiling.
240        let log_b_n = n.log2_est() / B.log2_est();
241        let num_terms = (self.precision as f32 / (2.0 * log_b_n)) as usize + 10;
242
243        let (_p, q, t) = crate::math::cache::iacoth_bs(n, 1, num_terms + 1);
244
245        // L(n) = (Q + T) / (n·Q). Extra guard digits absorb the division's rounding
246        // (the binary-splitting state is exact, so only this single round loses anything).
247        let guard_digits = self.base_guard_digits::<B>();
248        let work_context = Self::new(self.precision + guard_digits + 2);
249
250        let num = work_context.convert_int::<B>(q.as_ibig() + &t).value();
251        let denom = work_context.convert_int::<B>(IBig::from(n) * &q).value();
252        num / denom
253    }
254
255    /// Evaluate `ln(x)` (or `ln(x+1)` when `one_plus`) at `work_precision` via the atanh series,
256    /// returning a [`Ball`] whose radius is derived mechanically by Ball arithmetic (error
257    /// propagates term-by-term through the series; cancellation and the `s·ln(B)` reconstruction
258    /// flow through the Ball scale factors).
259    ///
260    /// This is the near-correct computation core shared by the public Ziv-backed `ln`/`ln_1p`
261    /// (which wrap it in a retry loop) and by `ln_base` (which only needs a near-correct constant
262    /// `ln(B)`). It lives on `R: Round` so those near-correct callers don't inherit the
263    /// `ErrorBounds` bound.
264    pub(crate) fn ln_compute<const B: Word>(
265        &self,
266        x: &Repr<B>,
267        mut work_precision: usize,
268        one_plus: bool,
269        mut cache: Option<&mut ConstCache>,
270    ) -> Ball<B> {
271        // Round the input to the working precision; the input's own rounding is the only error
272        // introduced here.
273        let context = Context::<mode::HalfEven>::new(work_precision);
274        let x_ball = Ball::from_rounded(context.repr_round_ref(x).map(|r| FBig::new(r, context)));
275
276        // When one_plus is true and |x| < 1/B, the input is fed into the Maclaurin without scaling.
277        let no_scaling = one_plus && x_ball.mid.log2_est() < -B.log2_est();
278
279        let (s, mut x_scaled) = if no_scaling {
280            (0, x_ball)
281        } else {
282            let x_ball = if one_plus {
283                x_ball.add(&Ball::exact_int(work_precision, IBig::ONE))
284            } else {
285                x_ball
286            };
287
288            let log2 = x_ball.mid.log2_bounds().0;
289            let s = log2 as isize - (log2 < 0.) as isize; // floor(log2(x))
290
291            let mut exact = x_ball.n.is_zero();
292            let x_scaled = if B == 2 {
293                x_ball.shift(s) // exact (power-of-base shift)
294            } else if s > 0 {
295                // Exact divisor 2^s: the error shrinks by it directly (no general-division rational).
296                x_ball.div_exact(&(IBig::ONE << s as usize))
297            } else {
298                // Scaling by 2^|s| is exact (finite decimal × power of two). Use the tracking
299                // variant so an exact operand keeps n = 0 — otherwise the unconditional `+1`
300                // would be amplified by `rescale_precision` into a `B^precision`-sized error
301                // count that never shrinks across Ziv retries (the powf-of-base-<1 hang).
302                x_ball.scale_int_tracking(&(IBig::ONE << (-s) as usize), &mut exact)
303            };
304            debug_assert!(x_scaled.mid >= FBig::<mode::HalfEven, B>::ONE);
305            (s, x_scaled)
306        };
307
308        // The reconstruction 2·sum + s·ln(B) *cancels* for x < 1 (s < 0), so the series runs at
309        // double precision to keep the pre-cancellation sum accurate. The finer ulp rescales `n`.
310        if s < 0 || x_scaled.mid.repr().sign() == Sign::Negative {
311            work_precision += self.precision;
312            x_scaled.rescale_precision(self.precision);
313        }
314        let work_context = Context::<mode::HalfEven>::new(work_precision);
315
316        // after the number is scaled to nearly one, use Maclaurin series on log(x) = 2atanh(z):
317        // let z = (x-1)/(x+1) < 1, log(x) = 2atanh(z) = 2Σ(z²ⁱ⁺¹/(2i+1)) for i = 1,3,5,...
318        let z = if no_scaling {
319            let two = Ball::exact_int(work_precision, IBig::from(2));
320            let den = x_scaled.add(&two);
321            x_scaled.div(&den)
322        } else {
323            let one = Ball::exact_int(work_precision, IBig::ONE);
324            let num = x_scaled.sub(&one);
325            let den = x_scaled.add(&one);
326            num.div(&den)
327        };
328        let z2 = z.mul(&z);
329        let mut pow = z.clone();
330        let mut sum = z;
331        let mut k: usize = 3;
332        loop {
333            pow = pow.mul(&z2);
334
335            let increase = pow.div_int(k);
336            if increase.mid.abs_cmp(&sum.mid.ulp_lb()).is_le() {
337                break;
338            }
339
340            sum = sum.add(&increase);
341            k += 2;
342        }
343
344        // Omitted series tail: the first omitted term is ≤ sum.ulp_lb(), and the tail of the
345        // atanh series shrinks by z² per step with 1/(1−z²) < B for x_scaled ∈ [1, B), so the
346        // tail is < B·sum.ulp_lb() < B ulps of sum.
347        sum.inflate(&IBig::from(B));
348
349        // compose the logarithm of the original number
350        let sum2 = sum.scale_int(&IBig::from(2));
351        if no_scaling {
352            sum2
353        } else {
354            // ln(2) as a ball. The constant evaluates the atanh series via binary splitting at
355            // work + guard digits and rounds once to `work_precision`, so its error is a handful
356            // of work-precision ulps; 8 is a conservative sound bound for every code path
357            // (cached and uncached).
358            let ln2 = work_context.ln2::<B>(reborrow_cache(&mut cache));
359            let ln2 = Ball::with_error(ln2, IBig::from(8));
360            sum2.add(&ln2.scale_int(&IBig::from(s)))
361        }
362    }
363
364    /// `ln(1 + arg)` of a *ball* input. [`ln_compute`](Self::ln_compute) evaluates the series on
365    /// `arg.mid`; the input ball's own error `|θ| ≤ arg.n·ulp(arg)` then contributes `|θ|/(1+arg)`
366    /// to the log. Bound via `(1+arg)`'s ball magnitude: for a mostly-correct argument the factor
367    /// `1/(1−|θ|/(1+arg)) ≤ 2` is sound, so the adjustment is `⌈2·n_arg·ulp_arg/((1+arg)·ulp_ln)⌉`.
368    pub(crate) fn ln_1p_ball<const B: Word>(
369        &self,
370        arg: &Ball<B>,
371        mut cache: Option<&mut ConstCache>,
372    ) -> Ball<B> {
373        let mut ln_ball =
374            self.ln_compute::<B>(arg.mid.repr(), self.precision, true, reborrow_cache(&mut cache));
375        let den = arg.add(&Ball::exact_int(self.precision, IBig::ONE));
376        let e_d = den.mid.repr().exponent; // (1+arg) = sig_d·B^(e_d)
377        let sig_d = den.mid.repr().significand.clone().abs();
378        // `lead_*` is the leading position (`lead_exp`), so `ulp_arg = B^(lead_arg − p_arg)` and
379        // `ulp_ln = B^(lead_ln − p_ln)`. The input error propagates as
380        //   n_arg·ulp_arg / ((1+arg)·ulp_ln) = n_arg·B^(lead_arg − p_arg − e_d − lead_ln + p_ln)/sig_d;
381        // ×2 for the 1/(1−|θ|/(1+arg)) factor.
382        // The precision difference is essential: `ln_compute`'s s<0 path runs at double precision,
383        // so `ln_ball` sits at 2·self.precision while `arg` stays at self.precision — dropping the
384        // `−p_arg+p_ln` term under-bounds the adjust by B^(p_ln−p_arg) (atanh(x<0) near the pole
385        // then mis-certifies, e.g. off by 2^13 ulps).
386        let lead_arg = Ball::lead_exp(&arg.mid);
387        let p_arg = arg.mid.precision();
388        let lead_ln = Ball::lead_exp(&ln_ball.mid);
389        let p_ln = ln_ball.mid.precision();
390        let shift = lead_arg - p_arg as isize - e_d - lead_ln + p_ln as isize;
391        let num = ceil_shift::<B>(2 * &arg.n, shift);
392        let adjust = (num + &sig_d - IBig::ONE) / sig_d;
393        ln_ball.inflate(&adjust);
394        ln_ball
395    }
396}
397
398// `ln`/`ln_1p` are correctly rounded via the Ziv loop, whose containment test needs the rounding
399// preimage (`R: ErrorBounds`). They delegate the series to `ln_compute`.
400impl<R: ErrorBounds> Context<R> {
401    /// Calculate the natural logarithm function (`log(x)`) on the float number under this context.
402    ///
403    /// # Examples
404    ///
405    /// ```
406    /// # use core::str::FromStr;
407    /// # use dashu_base::ParseError;
408    /// # use dashu_float::DBig;
409    /// use dashu_base::Approximation::*;
410    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
411    ///
412    /// let context = Context::<HalfAway>::new(2);
413    /// let a = DBig::from_str("1.234")?;
414    /// assert_eq!(context.ln(&a.repr(), None), Ok(Inexact(DBig::from_str("0.21")?, NoOp)));
415    /// # Ok::<(), ParseError>(())
416    /// ```
417    #[inline]
418    pub fn ln<const B: Word>(
419        &self,
420        x: &Repr<B>,
421        cache: Option<&mut ConstCache>,
422    ) -> FpResult<FBig<R, B>> {
423        if x.is_infinite() {
424            return Err(FpError::InfiniteInput);
425        }
426        if x.significand.is_zero() {
427            // ln(±0) = -inf (a value, not an error)
428            return Ok(Exact(FBig::new(Repr::neg_infinity(), *self)));
429        }
430        if x.sign() == Sign::Negative {
431            return Err(FpError::OutOfDomain);
432        }
433        self.ln_internal(x, false, cache)
434    }
435
436    /// Calculate the natural logarithm function (`log(x+1)`) on the float number under this context.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// # use core::str::FromStr;
442    /// # use dashu_base::ParseError;
443    /// # use dashu_float::DBig;
444    /// use dashu_base::Approximation::*;
445    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
446    ///
447    /// let context = Context::<HalfAway>::new(2);
448    /// let a = DBig::from_str("0.1234")?;
449    /// assert_eq!(context.ln_1p(&a.repr(), None), Ok(Inexact(DBig::from_str("0.12")?, AddOne)));
450    /// # Ok::<(), ParseError>(())
451    /// ```
452    #[inline]
453    pub fn ln_1p<const B: Word>(
454        &self,
455        x: &Repr<B>,
456        cache: Option<&mut ConstCache>,
457    ) -> FpResult<FBig<R, B>> {
458        if x.is_infinite() {
459            return Err(FpError::InfiniteInput);
460        }
461        // Domain of ln_1p is x > -1. x == -1 gives -inf; x < -1 is out of domain.
462        if x.sign() == Sign::Negative && !x.significand.is_zero() {
463            match FBig::<R, B>::new(x.clone(), *self).abs_cmp(&FBig::ONE) {
464                Ordering::Greater => return Err(FpError::OutOfDomain), // x < -1
465                Ordering::Equal => return Ok(Exact(FBig::new(Repr::neg_infinity(), *self))),
466                _ => {}
467            }
468        }
469        self.ln_internal(x, true, cache)
470    }
471
472    fn ln_internal<const B: Word>(
473        &self,
474        x: &Repr<B>,
475        one_plus: bool,
476        mut cache: Option<&mut ConstCache>,
477    ) -> FpResult<FBig<R, B>> {
478        assert_finite(x);
479
480        // Exact special cases first: they need no rounding, so a precision-0 (unlimited)
481        // value such as `FBig::ONE` or the one from `try_from(0.0)` must still resolve
482        // ln/ln_1p exactly rather than tripping the limited-precision assertion below.
483        if !one_plus && x.is_one() {
484            return Ok(Exact(FBig::ZERO)); // ln(1) = +0
485        }
486        if one_plus && x.significand.is_zero() {
487            // ln_1p(±0) = ±0
488            let zero = if x.is_neg_zero() {
489                FBig::new(Repr::neg_zero(), *self)
490            } else {
491                FBig::ZERO
492            };
493            return Ok(Exact(zero));
494        }
495
496        assert_limited_precision(self.precision);
497
498        // Correct rounding via the Ziv loop: `ln_compute` evaluates the atanh series at `p + guard`
499        // and reports a provable error radius; the driver retries with more guard digits until the
500        // approximation's error interval lies entirely inside one rounding bin. The guard is a
501        // *performance* knob (first-attempt hit rate), not a correctness backstop — Ziv certifies
502        // the result. (The pre-Ziv `+ 2` is retained: with the conservative radius below it is still
503        // needed for the first attempt to clear the half-ulp preimage at typical precisions.)
504        let base_guard = self.base_guard_digits::<B>() + 2;
505        self.ziv(base_guard + one_plus as usize, |guard| {
506            Ok(self
507                .ln_compute::<B>(x, self.precision + guard, one_plus, reborrow_cache(&mut cache))
508                .to_value_radius::<R>())
509        })
510    }
511
512    /// Calculate the base-2 logarithm (`log2(x)`) on the float number under this context.
513    ///
514    /// Correctly rounded to the context's precision under any rounding mode; for an exact power
515    /// of two the result is the exact integer `log2(x)`.
516    ///
517    /// # Domain
518    ///
519    /// `log2(±0) = −∞` and a negative (non-zero) input is out of domain; an infinite input is an
520    /// error (a finite context cannot produce the infinite `log2(+∞) = +∞` exactly).
521    ///
522    /// # Examples
523    ///
524    /// ```
525    /// # use core::str::FromStr;
526    /// # use dashu_base::ParseError;
527    /// # use dashu_float::DBig;
528    /// use dashu_base::Approximation::*;
529    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
530    ///
531    /// let context = Context::<HalfAway>::new(4);
532    /// let a = DBig::from_str("10")?;
533    /// assert_eq!(context.log2(&a.repr(), None), Ok(Inexact(DBig::from_str("3.322")?, AddOne)));
534    /// # Ok::<(), ParseError>(())
535    /// ```
536    #[inline]
537    pub fn log2<const B: Word>(
538        &self,
539        x: &Repr<B>,
540        cache: Option<&mut ConstCache>,
541    ) -> FpResult<FBig<R, B>> {
542        if x.is_infinite() {
543            return Err(FpError::InfiniteInput);
544        }
545        if x.significand.is_zero() {
546            // log2(±0) = -inf (a value, not an error)
547            return Ok(Exact(FBig::new(Repr::neg_infinity(), *self)));
548        }
549        if x.sign() == Sign::Negative {
550            return Err(FpError::OutOfDomain);
551        }
552        self.log2_internal(x, cache)
553    }
554
555    fn log2_internal<const B: Word>(
556        &self,
557        x: &Repr<B>,
558        mut cache: Option<&mut ConstCache>,
559    ) -> FpResult<FBig<R, B>> {
560        assert_finite(x);
561
562        // Exact shortcuts first — they also cover unlimited precision, which the Ziv loop below
563        // rejects via its limited-precision assertion.
564        if x.is_one() {
565            return Ok(Exact(FBig::ZERO)); // log2(1) = +0
566        }
567
568        // Exact power-of-two shortcut: if x = 2^k for an integer k, log2(x) = k. This is *required*
569        // for directed rounding — the Ziv loop below cannot certify an exactly-representable
570        // result whose true value sits on a rounding boundary (its shrinking error interval
571        // always straddles the boundary), so without this shortcut log2(2^-159) under `Up` would
572        // exhaust the retry cap and return k + 1 ulp instead of the exact k.
573        //
574        // log2(x) = log2(significand) + exponent·log2(B). With significand = 2^m this is an exact
575        // integer whenever log2(B) is integral (B a power of two), or — for a non-power-of-two
576        // base — when the exponent is zero.
577        let mag = (&x.significand).unsigned_abs();
578        if mag.is_power_of_two() && (x.exponent == 0 || B.is_power_of_two()) {
579            let m = mag.trailing_zeros().unwrap(); // = log2(significand)
580            let log2_b = B.trailing_zeros() as isize;
581            let k = IBig::from(m) + IBig::from(x.exponent) * IBig::from(log2_b);
582            return Ok(self.convert_int::<B>(k));
583        }
584
585        assert_limited_precision(self.precision);
586
587        // log2(x) = ln(x)/ln(2), correctly rounded via the Ziv loop. Both logarithms come from the
588        // Ball-based `ln_compute`, and dividing them as Balls composes the radius mechanically:
589        // the quotient's error is bounded from the two logarithms' relative errors, with no
590        // directed-interval bookkeeping or guard-digit constant. The driver certifies the result
591        // against the rounding preimage exactly as before.
592        let initial_guard = self.base_guard_digits::<B>() + 4;
593        self.ziv(initial_guard, |guard| {
594            let work_precision = self.precision + guard;
595            let lx = self.ln_compute::<B>(x, work_precision, false, reborrow_cache(&mut cache));
596            let two = Repr::new(IBig::from(2), 0);
597            let l2 = self.ln_compute::<B>(&two, work_precision, false, reborrow_cache(&mut cache));
598            Ok(lx.div(&l2).to_value_radius::<R>())
599        })
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606    use crate::round::mode;
607    use alloc::vec::Vec;
608    use dashu_base::BitTest;
609
610    #[test]
611    fn test_ln_zero_is_neg_infinity() {
612        let ctx = Context::<mode::HalfEven>::new(53);
613        let r = ctx.ln::<2>(&Repr::<2>::zero(), None).unwrap().value();
614        assert!(r.repr().is_infinite());
615        assert_eq!(r.repr().sign(), Sign::Negative);
616    }
617
618    #[test]
619    fn test_iacoth() {
620        let context = Context::<mode::Zero>::new(10);
621        let binary_6 = context.iacoth::<2>(6.into()).with_precision(10).value();
622        assert_eq!(binary_6.repr.significand, IBig::from(689));
623        let decimal_6 = context.iacoth::<10>(6.into()).with_precision(10).value();
624        assert_eq!(decimal_6.repr.significand, IBig::from(1682361183));
625
626        let context = Context::<mode::Zero>::new(40);
627        let decimal_6 = context.iacoth::<10>(6.into()).with_precision(40).value();
628        assert_eq!(
629            decimal_6.repr.significand,
630            IBig::from_str_radix("1682361183106064652522967051084960450557", 10).unwrap()
631        );
632
633        let context = Context::<mode::Zero>::new(201);
634        let binary_6 = context.iacoth::<2>(6.into()).with_precision(201).value();
635        assert_eq!(
636            binary_6.repr.significand,
637            IBig::from_str_radix(
638                "2162760151454160450909229890833066944953539957685348083415205",
639                10
640            )
641            .unwrap()
642        );
643    }
644
645    #[test]
646    fn test_ln2_ln10() {
647        let context = Context::<mode::Zero>::new(45);
648        let decimal_ln2 = context.ln2::<10>(None).with_precision(45).value();
649        assert_eq!(
650            decimal_ln2.repr.significand,
651            IBig::from_str_radix("693147180559945309417232121458176568075500134", 10).unwrap()
652        );
653        let decimal_ln10 = context.ln10::<10>(None).with_precision(45).value();
654        assert_eq!(
655            decimal_ln10.repr.significand,
656            IBig::from_str_radix("230258509299404568401799145468436420760110148", 10).unwrap()
657        );
658
659        let context = Context::<mode::Zero>::new(180);
660        let binary_ln2 = context.ln2::<2>(None).with_precision(180).value();
661        assert_eq!(
662            binary_ln2.repr.significand,
663            IBig::from_str_radix("1062244963371879310175186301324412638028404515790072203", 10)
664                .unwrap()
665        );
666        let binary_ln10 = context.ln10::<2>(None).with_precision(180).value();
667        assert_eq!(
668            binary_ln10.repr.significand,
669            IBig::from_str_radix("882175346869410758689845931257775553286341791676474847", 10)
670                .unwrap()
671        );
672    }
673
674    #[test]
675    fn test_log2_domain() {
676        let ctx = Context::<mode::HalfEven>::new(53);
677        // log2(±0) = -inf (a value, not an error)
678        let r = ctx.log2::<2>(&Repr::<2>::zero(), None).unwrap().value();
679        assert!(r.repr.is_infinite());
680        assert_eq!(r.repr.sign(), Sign::Negative);
681        // log2(negative) is out of domain
682        assert!(matches!(
683            ctx.log2::<2>(&Repr::new((-1).into(), 0), None),
684            Err(FpError::OutOfDomain)
685        ));
686        // an infinite input is rejected
687        assert!(matches!(ctx.log2::<2>(&Repr::infinity(), None), Err(FpError::InfiniteInput)));
688    }
689
690    #[test]
691    fn test_log2_exact_power_of_two() {
692        // log2(2^k) = k exactly under every rounding mode. Regression for the directed-rounding
693        // defect: rounding ln(x) and ln(2) each toward the mode and dividing once does not bound
694        // the quotient, so previously log2(2^-159) under `Up` returned -159 + 1 ulp.
695        let p = 53;
696        for k in [0isize, 1, -1, 5, 159, -159, 1000, -1000] {
697            let x = Repr::<2>::new(IBig::from(1), k); // 2^k
698            let r_down = Context::<mode::Down>::new(p)
699                .log2::<2>(&x, None)
700                .unwrap()
701                .value();
702            let r_up = Context::<mode::Up>::new(p)
703                .log2::<2>(&x, None)
704                .unwrap()
705                .value();
706            let r_zero = Context::<mode::Zero>::new(p)
707                .log2::<2>(&x, None)
708                .unwrap()
709                .value();
710            let r_he = Context::<mode::HalfEven>::new(p)
711                .log2::<2>(&x, None)
712                .unwrap()
713                .value();
714            // Every directed mode produces the identical value — no mode-dependent ulp.
715            assert_eq!(r_down.repr, r_he.repr, "Down != HalfEven for log2(2^{k})");
716            assert_eq!(r_up.repr, r_he.repr, "Up != HalfEven for log2(2^{k})");
717            assert_eq!(r_zero.repr, r_he.repr, "Zero != HalfEven for log2(2^{k})");
718            // And that value is exactly k.
719            assert_eq!(r_he.to_int().value(), IBig::from(k), "value for log2(2^{k})");
720        }
721    }
722
723    #[test]
724    fn test_log2_exact_power_of_two_decimal_base() {
725        // In a non-power-of-two base the shortcut still fires when the exponent is zero: a
726        // significand that is itself a power of two makes x = 2^m exactly.
727        let p = 53;
728        for (sig, want) in [(8i32, 3isize), (1024, 10), (2, 1), (32, 5)] {
729            let x = Repr::<10>::new(IBig::from(sig), 0);
730            let r_down = Context::<mode::Down>::new(p)
731                .log2::<10>(&x, None)
732                .unwrap()
733                .value();
734            let r_up = Context::<mode::Up>::new(p)
735                .log2::<10>(&x, None)
736                .unwrap()
737                .value();
738            let r_he = Context::<mode::HalfEven>::new(p)
739                .log2::<10>(&x, None)
740                .unwrap()
741                .value();
742            assert_eq!(r_down.repr, r_he.repr, "Down != HalfEven for log2({sig}) base 10");
743            assert_eq!(r_up.repr, r_he.repr, "Up != HalfEven for log2({sig}) base 10");
744            assert_eq!(r_he.to_int().value(), IBig::from(want), "value for log2({sig}) base 10");
745        }
746    }
747
748    /// For a non-power-of-two significand `sig` (so `log2` is irrational and never lands on a
749    /// rounding boundary), each directed result must equal a high-precision oracle rounded to the
750    /// target precision under the same mode — the definition of correct rounding.
751    fn check_log2_directed_matches_oracle<const B: Word>(sig: u32, p: usize) {
752        let oracle_ctx = Context::<mode::HalfEven>::new(p + 40);
753        let x = Repr::<B>::new(IBig::from(sig), 0);
754        let oracle = oracle_ctx.log2::<B>(&x, None).unwrap().value();
755
756        let want_down = Context::<mode::Down>::new(p)
757            .repr_round_ref(&oracle.repr)
758            .value();
759        let want_up = Context::<mode::Up>::new(p)
760            .repr_round_ref(&oracle.repr)
761            .value();
762        let want_he = Context::<mode::HalfEven>::new(p)
763            .repr_round_ref(&oracle.repr)
764            .value();
765
766        let got_down = Context::<mode::Down>::new(p)
767            .log2::<B>(&x, None)
768            .unwrap()
769            .value();
770        let got_up = Context::<mode::Up>::new(p)
771            .log2::<B>(&x, None)
772            .unwrap()
773            .value();
774        let got_he = Context::<mode::HalfEven>::new(p)
775            .log2::<B>(&x, None)
776            .unwrap()
777            .value();
778
779        assert_eq!(got_down.repr, want_down, "log2({sig}) base {B} under Down");
780        assert_eq!(got_up.repr, want_up, "log2({sig}) base {B} under Up");
781        assert_eq!(got_he.repr, want_he, "log2({sig}) base {B} under HalfEven");
782    }
783
784    #[test]
785    fn test_log2_directed_matches_oracle() {
786        let p = 24;
787        for sig in [3u32, 7, 10, 12345, 65537] {
788            check_log2_directed_matches_oracle::<2>(sig, p);
789        }
790        // Exercise a non-power-of-two base through the Ziv interval path too.
791        for sig in [3u32, 7, 10, 12345] {
792            check_log2_directed_matches_oracle::<10>(sig, p);
793        }
794    }
795
796    // log2 of a value whose result sits within ~1 work-ulp of a power of two must still round to
797    // the correct neighbor under directed modes. log2(f64::MAX) ≈ 1024 − 2^-53/ln2 sits just below
798    // 1024; under Down at p=53 the answer is 1024 − 2^-42 (the largest p=53 value ≤ it), but an
799    // unsound radius previously let Ziv certify 1024 on the first attempt.
800    #[test]
801    fn test_log2_just_below_power_of_two_directed() {
802        let x = FBig::<mode::HalfEven, 2>::try_from(f64::MAX).unwrap();
803        // High-precision oracle, then re-rounded to the target precision under each mode.
804        let oracle = Context::<mode::HalfEven>::new(200)
805            .log2::<2>(x.repr(), None)
806            .unwrap()
807            .value();
808        for p in [24usize, 40, 53, 64] {
809            let want_down = Context::<mode::Down>::new(p)
810                .repr_round_ref(&oracle.repr)
811                .value();
812            let want_up = Context::<mode::Up>::new(p)
813                .repr_round_ref(&oracle.repr)
814                .value();
815            let got_down = Context::<mode::Down>::new(p)
816                .log2::<2>(x.repr(), None)
817                .unwrap()
818                .value();
819            let got_up = Context::<mode::Up>::new(p)
820                .log2::<2>(x.repr(), None)
821                .unwrap()
822                .value();
823            assert_eq!(got_down.repr(), &want_down, "p={p} Down");
824            assert_eq!(got_up.repr(), &want_up, "p={p} Up");
825            // Directed invariant: Up ≥ Down.
826            assert!(got_up.repr() >= got_down.repr(), "p={p} Up < Down");
827        }
828    }
829
830    /// Directed `ln` of `x ∈ [1, 2)` must match a high-precision oracle re-rounded under the same
831    /// mode. This binade (s = 0) is where the radius under-estimated the error: `result` inherits
832    /// `ln_base`'s over-delivered context, and for `x` just above 1 the scaling even classifies
833    /// `s = −1`, so `2·sum + s·ln2` cancels and the error stays at `sum`'s magnitude while
834    /// `result`'s collapses — both make `result.ulp()` the wrong scale for the radius.
835    fn check_ln_directed_in_unit_binade(k: usize, p: usize) {
836        // x = (2^k + 1) * 2^-k = 1 + 2^-k, exactly representable at precision p when k < p.
837        let x = Repr::<2>::new(IBig::from(1i64 << k) + IBig::ONE, -(k as isize));
838        let oracle = Context::<mode::HalfEven>::new(p + 60)
839            .ln::<2>(&x, None)
840            .unwrap()
841            .value();
842        let want_down = Context::<mode::Down>::new(p)
843            .repr_round_ref(&oracle.repr)
844            .value();
845        let want_up = Context::<mode::Up>::new(p)
846            .repr_round_ref(&oracle.repr)
847            .value();
848        let got_down = Context::<mode::Down>::new(p)
849            .ln::<2>(&x, None)
850            .unwrap()
851            .value();
852        let got_up = Context::<mode::Up>::new(p)
853            .ln::<2>(&x, None)
854            .unwrap()
855            .value();
856        assert_eq!(got_down.repr(), &want_down, "ln(1+2^-{k}) p={p} Down");
857        assert_eq!(got_up.repr(), &want_up, "ln(1+2^-{k}) p={p} Up");
858        assert!(got_up.repr() >= got_down.repr(), "ln(1+2^-{k}) p={p} Up < Down");
859    }
860
861    #[test]
862    fn test_ln_directed_near_one() {
863        // Sweep the near-1 binade at low precision, including the k close to p cases that
864        // classify as s = −1 and cancel.
865        for p in [24usize, 40, 53] {
866            for k in 1..p.saturating_sub(1) {
867                check_ln_directed_in_unit_binade(k, p);
868            }
869        }
870    }
871
872    /// Fixed inputs for the `log2` oracle differential: moderate magnitudes and the
873    /// near-boundary regimes the legacy directed-interval implementation was specifically sized for.
874    fn log2_diff_inputs() -> Vec<Repr<2>> {
875        let mut v = Vec::new();
876        for x in [0.5f64, 1.5, 2.0, 3.0, 10.0, 1000.0, 1e-6, 123.456, 2.5e-10] {
877            v.push(FBig::<mode::HalfEven, 2>::try_from(x).unwrap().into_repr());
878        }
879        // Exact powers of two.
880        for k in [-100isize, -50, -10, -1, 0, 1, 10, 50, 100] {
881            v.push(Repr::new(IBig::ONE, k));
882        }
883        // Just below the largest f64 (log2 ≈ 1024, the directed-regime case in the old comment).
884        v.push(
885            FBig::<mode::HalfEven, 2>::try_from(f64::MAX)
886                .unwrap()
887                .into_repr(),
888        );
889        // The [1, 2) unit binade and its mirror below 1: 1 ± 2^-k and 2 − 2^-k exercise the
890        // s = −1 cancellation (the second-classified-s-−1 case the doubling compensates).
891        for k in 1usize..=60 {
892            v.push(Repr::new(IBig::from(1u64 << k) + IBig::ONE, -(k as isize))); // 1 + 2^-k
893            v.push(Repr::new(IBig::from((1u64 << k) - 1), -(k as isize))); // 1 − 2^-k
894            v.push(Repr::new(IBig::from((1u64 << (k + 1)) - 1), -(k as isize)));
895            // 2 − 2^-k
896        }
897        v
898    }
899
900    /// The Ball-based `log2` must round exactly like a high-precision oracle (the definition of
901    /// correct rounding) across precisions, modes, and the near-boundary inputs.
902    ///
903    /// The legacy directed-interval implementation is *not* used as the oracle: it has its own
904    /// residual 1-ulp bug under directed rounding for `log2(1 − 2^-k)` at p=50 (verified against
905    /// an independent high-precision computation) — exactly the class of defect this pilot
906    /// replaces.
907    fn check_log2_differential<R: ErrorBounds>(p: usize, x: &Repr<2>, oracle: &Repr<2>) {
908        let ctx = Context::<R>::new(p);
909        let want = ctx.repr_round_ref(oracle).value();
910        let got = ctx.log2_internal::<2>(x, None).unwrap().value();
911        assert_eq!(got.repr, want, "p={p} {} x={x:?}", core::any::type_name::<R>(),);
912    }
913
914    /// Regression: `ln_compute`'s s<0 path (base < 1) must NOT inflate its error count with the
915    /// working precision. An exactly-representable input scaled by a power of two is exact, so the
916    /// radius must shrink monotonically as the work precision grows — otherwise the composed
917    /// `pow_exp_log` chain's radius stays constant and the Ziv loop hangs (powf of a base < 1).
918    #[test]
919    fn ln_small_base_radius_shrinks_with_guard() {
920        let ctx = Context::<mode::HalfEven>::new(50);
921        // 0.2668 (base 10): s = floor(log2(0.2668)) = -2, the s < 0 path.
922        let x = Repr::<10>::new(IBig::from(2668), -4);
923        for guard in [4usize, 12, 40, 120] {
924            let ball = ctx.ln_compute::<10>(&x, 50 + guard, false, None);
925            // The regression: n must be O(series terms) (~10^5, bit_len < 30), NOT inflated to
926            // ~B^50 ≈ 10^50 (bit_len ~166) by the s<0 reduction's spurious +1.
927            assert!(
928                ball.n.bit_len() < 30,
929                "n = {} ({} bits) too large at guard={guard}: the s<0 reduction inflated it",
930                ball.n,
931                ball.n.bit_len()
932            );
933            // The radius in target (precision 50) ulps must fit a preimage so Ziv certifies on the
934            // first attempt: n·B^(E−p_ball)·B^(50−E) ≤ 1.
935            let radius_target = crate::ball::ceil_shift::<10>(
936                ball.n.clone(),
937                Ball::lead_exp(&ball.mid) - ball.mid.precision() as isize + 50,
938            );
939            assert!(
940                radius_target <= IBig::ONE,
941                "radius {radius_target} ulps at guard={guard} does not certify (n={})",
942                ball.n
943            );
944        }
945    }
946
947    #[test]
948    fn log2_ball_matches_oracle() {
949        let inputs = log2_diff_inputs();
950        // Moderate precisions: full input sweep, all five modes.
951        for p in [20usize, 50, 100] {
952            for x in &inputs {
953                // The oracle is mode-independent: a high-precision HalfEven value re-rounded
954                // under each target mode.
955                let oracle = Context::<mode::HalfEven>::new(p + 60)
956                    .log2::<2>(x, None)
957                    .unwrap()
958                    .value();
959                check_log2_differential::<mode::HalfEven>(p, x, &oracle.repr);
960                check_log2_differential::<mode::Down>(p, x, &oracle.repr);
961                check_log2_differential::<mode::Up>(p, x, &oracle.repr);
962                check_log2_differential::<mode::Zero>(p, x, &oracle.repr);
963                check_log2_differential::<mode::Away>(p, x, &oracle.repr);
964            }
965        }
966        // The arbitrary-precision regime: a reduced sweep (directed modes still exercised).
967        for x in inputs.iter().step_by(9) {
968            let oracle = Context::<mode::HalfEven>::new(560)
969                .log2::<2>(x, None)
970                .unwrap()
971                .value();
972            check_log2_differential::<mode::HalfEven>(500, x, &oracle.repr);
973            check_log2_differential::<mode::Down>(500, x, &oracle.repr);
974            check_log2_differential::<mode::Up>(500, x, &oracle.repr);
975        }
976    }
977
978    #[test]
979    fn ln_1p_ball_bounds_negative_arg() {
980        // Regression: `ln_1p_ball`'s input-error adjust dropped the precision-difference term
981        // (−p_arg+p_ln). For an arg with 1+arg ∈ (0, 1) (e.g. atanh(x<0) near the pole),
982        // `ln_compute` doubles the work precision (the s<0 path), so `ln_ball` sits at 2p while
983        // `arg` stays at p — the missing +p under-bounded the adjust by B^p and the radius no
984        // longer covered the true value.
985        use crate::fbig::FBig;
986        use crate::repr::Context;
987        type F = FBig<mode::HalfEven, 10>;
988        let ctx = Context::<mode::HalfEven>::new(10);
989        // arg mid = −0.9999 at precision 10 (ulp = 1e-10), n = 5 ⇒ true arg = −0.9999000005.
990        let mid = F::from_parts(IBig::from(-9999000000i64), -10)
991            .with_precision(10)
992            .value();
993        let arg = Ball::<10>::with_error(mid, IBig::from(5));
994        let ln_ball = ctx.ln_1p_ball::<10>(&arg, None);
995        // true ln(1+arg) = ln(1 − 0.9999000005) = ln(9.99995e-5), oracle at precision 60.
996        let one_plus_true = F::from_parts(IBig::from(999995i64), -10)
997            .with_precision(0)
998            .value();
999        let true_ln = one_plus_true
1000            .with_precision(60)
1001            .value()
1002            .ln()
1003            .with_precision(0)
1004            .value();
1005        let diff = (ln_ball.mid.clone().with_precision(0).value() - true_ln).abs();
1006        let bound = F::from(ln_ball.n.clone()) * ln_ball.mid.ulp().with_precision(0).value();
1007        assert!(
1008            diff <= bound,
1009            "ln_1p_ball: |mid − true| = {diff} > n·ulp = {bound} (n = {}, missing −p_arg+p_ln?)",
1010            ln_ball.n
1011        );
1012    }
1013}