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}