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