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