Skip to main content

decimal_scaled/
log_exp_strict.rs

1//! Logarithm and exponential methods for [`D38`].
2//!
3//! # Methods
4//!
5//! - **Logarithms:** [`D38::ln`] / [`D38::log`] / [`D38::log2`] / [`D38::log10`].
6//! - **Exponentials:** [`D38::exp`] / [`D38::exp2`].
7//!
8//! # The four-variant matrix
9//!
10//! Each function ships four entry points so a single name covers
11//! every (precision × rounding) combination:
12//!
13//! | Method            | Guard width    | Rounding mode               |
14//! |-------------------|----------------|------------------------------|
15//! | `<fn>_strict`     | crate default  | crate default               |
16//! | `<fn>_strict_with`| crate default  | caller-supplied              |
17//! | `<fn>_approx`     | caller-chosen  | crate default               |
18//! | `<fn>_approx_with`| caller-chosen  | caller-supplied              |
19//!
20//! `_strict` runs at `SCALE + STRICT_GUARD` (const-folded so LLVM
21//! specialises one optimal kernel per `SCALE`). `_approx` runs at
22//! `SCALE + working_digits` chosen at call time — drop below
23//! `STRICT_GUARD` to trade precision for latency (the Mercator /
24//! Taylor series shortens proportionally), raise above for more
25//! headroom on chained compositions. When `working_digits ==
26//! STRICT_GUARD` the `_approx_with` body redirects to `_strict_with`
27//! so the const-folded path is never displaced.
28//!
29//! `ln_strict` uses range reduction plus a Mercator series;
30//! `exp_strict` uses range reduction plus a Taylor series; the
31//! remaining methods compose those two. All four variants are
32//! integer-only, `no_std`-compatible, and correctly rounded under
33//! the selected mode.
34//!
35//! Without the `strict` feature, the plain `<fn>` is an f64-bridge
36//! (calls the inherent `f64` intrinsic, gated on `std`). With
37//! `strict` it dispatches to `<fn>_strict`. See `docs/strict-mode.md`
38//! for the full dual-API and feature rules.
39//!
40//! # Precision
41//!
42//! The f64-bridge forms are **Lossy** — `self` round-trips through
43//! `f64`. Every `_strict` / `_strict_with` / `_approx` /
44//! `_approx_with` form is **correctly rounded** under the selected
45//! [`RoundingMode`]: the result is within 0.5 ULP of the exact
46//! value. They evaluate the series in the `d_w128_kernels::Fixed`
47//! guard-digit intermediate and round once at the end.
48//!
49//! [`RoundingMode`]: crate::RoundingMode
50//!
51//! # Domain handling
52//!
53//! `f64::ln`, `f64::log2`, `f64::log10`, and `f64::log` return `-Infinity`
54//! for `0.0` and `NaN` for negative inputs. The f64 bridge maps `NaN` to
55//! `D38::ZERO` and saturates infinities to `D38::MAX` or `D38::MIN`.
56//! The `*_strict` forms panic on out-of-domain inputs (`self <= 0`).
57
58use crate::core_type::D38;
59
60// ─────────────────────────────────────────────────────────────────────
61// Correctly-rounded strict log / exp core.
62//
63// The strict `ln` / `log` / `log2` / `log10` / `exp` / `exp2` all run
64// on a 256-bit `Fixed` intermediate at `SCALE + GUARD` working digits.
65// The 30 guard digits bound the total accumulated rounding error far
66// below 0.5 ULP of the output, so each result — rounded once,
67// half-to-even, back to `SCALE` — is correctly rounded.
68//
69// `GUARD = 30` keeps the working scale `W = SCALE + 30 <= 68` for
70// `SCALE <= 38`, which is small enough that the 64-digit constants
71// cover it, `r · 10^GUARD` fits `U256`, and the 512-bit mul/div
72// intermediates never overflow.
73// ─────────────────────────────────────────────────────────────────────
74
75pub(crate) const STRICT_GUARD: u32 = 30;
76
77/// `ln(2)` as a `Fixed` at working scale `w` (`w <= 64`). The constant
78/// is embedded to 64 fractional digits and narrowed to `w`.
79pub(crate) fn wide_ln2(w: u32) -> crate::d_w128_kernels::Fixed {
80    // ln 2 = 0.693147180559945309417232121458176568075500134360255254120680 0094
81    crate::d_w128_kernels::Fixed::from_decimal_split(
82        69_314_718_055_994_530_941_723_212_145_817_u128,
83        65_680_755_001_343_602_552_541_206_800_094_u128,
84    )
85    .rescale_down(64, w)
86}
87
88/// `ln(10)` as a `Fixed` at working scale `w` (`w <= 63`). Embedded to
89/// 63 fractional digits (`ln 10 ≈ 2.30…` has an integer digit) and
90/// narrowed to `w`.
91fn wide_ln10(w: u32) -> crate::d_w128_kernels::Fixed {
92    // ln 10 = 2.302585092994045684017991454684364207601101488628772976033327 901
93    crate::d_w128_kernels::Fixed::from_decimal_split(
94        23_025_850_929_940_456_840_179_914_546_843_u128,
95        64_207_601_101_488_628_772_976_033_327_901_u128,
96    )
97    .rescale_down(63, w)
98}
99
100/// Natural logarithm of a positive working-scale value `v_w`, returned
101/// at the same working scale `w`.
102///
103/// Range-reduces `v = 2^k · m` with `m ∈ [1,2)` — the mantissa is
104/// recomputed exactly from `v_w` once `k` is known — then evaluates
105/// `ln(m) = 2·artanh((m-1)/(m+1))` (`t ∈ [0,1/3]`, fast convergence)
106/// and returns `k·ln(2) + ln(m)`.
107pub(crate) fn ln_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
108    use crate::d_w128_kernels::Fixed;
109    let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
110    let two_w = one_w.double();
111
112    // Range reduction: find k with v ∈ [2^k, 2^(k+1)); m_w = v_w / 2^k.
113    let mut k: i32 = v_w.bit_length() as i32 - one_w.bit_length() as i32;
114    let m_w = loop {
115        let m = if k >= 0 {
116            v_w.shr(k as u32)
117        } else {
118            v_w.shl((-k) as u32)
119        };
120        if m.ge_mag(two_w) {
121            k += 1;
122        } else if !m.ge_mag(one_w) {
123            k -= 1;
124        } else {
125            break m;
126        }
127    };
128
129    // t = (m - 1) / (m + 1) ∈ [0, 1/3]; artanh(t) = t + t³/3 + t⁵/5 + …
130    let t = m_w.sub(one_w).div(m_w.add(one_w), w);
131    let t2 = t.mul(t, w);
132    let mut sum = t;
133    let mut term = t;
134    let mut j: u128 = 1;
135    loop {
136        term = term.mul(t2, w);
137        let contrib = term.div_small(2 * j + 1);
138        if contrib.is_zero() {
139            break;
140        }
141        sum = sum.add(contrib);
142        j += 1;
143        if j > 400 {
144            break;
145        }
146    }
147    let ln_m = sum.double();
148
149    let ln2 = wide_ln2(w);
150    let k_ln2 = if k >= 0 {
151        ln2.mul_u128(k as u128)
152    } else {
153        ln2.mul_u128((-k) as u128).neg()
154    };
155    k_ln2.add(ln_m)
156}
157
158/// `e` raised to a working-scale value `v_w`, returned at the same
159/// working scale `w`.
160///
161/// Range-reduces `v = k·ln(2) + s` with `|s| ≤ ln(2)/2`, evaluates the
162/// Taylor series for `exp(s)`, then reassembles `2^k · exp(s)` by
163/// shifting the working-scale value (so the `2^k` factor never
164/// amplifies a rounding error).
165///
166/// # Panics
167///
168/// Panics if `2^k · exp(s)` cannot fit a 256-bit working value — i.e.
169/// the caller's result would overflow its representable range.
170pub(crate) fn exp_fixed(v_w: crate::d_w128_kernels::Fixed, w: u32) -> crate::d_w128_kernels::Fixed {
171    use crate::d_w128_kernels::Fixed;
172    let one_w = Fixed { negative: false, mag: Fixed::pow10(w) };
173    let ln2 = wide_ln2(w);
174
175    // k = round(v / ln 2); s = v - k·ln(2), |s| <= ln(2)/2.
176    let k = v_w.div(ln2, w).round_to_nearest_int(w);
177    let k_ln2 = if k >= 0 {
178        ln2.mul_u128(k as u128)
179    } else {
180        ln2.mul_u128((-k) as u128).neg()
181    };
182    let s = v_w.sub(k_ln2);
183
184    // Taylor series exp(s) = 1 + s + s²/2! + … — `term` carries sⁿ/n!.
185    let mut sum = one_w;
186    let mut term = one_w;
187    let mut n: u128 = 1;
188    loop {
189        term = term.mul(s, w).div_small(n);
190        if term.is_zero() {
191            break;
192        }
193        sum = sum.add(term);
194        n += 1;
195        if n > 400 {
196            break;
197        }
198    }
199
200    // exp(v) = 2^k · exp(s).
201    if k >= 0 {
202        let shift = k as u32;
203        assert!(sum.bit_length() + shift <= 256, "D38::exp: result overflows the representable range");
204        sum.shl(shift)
205    } else {
206        sum.shr((-k) as u32)
207    }
208}
209
210impl<const SCALE: u32> D38<SCALE> {
211    // Logarithms
212
213    /// Returns the natural logarithm (base e) of `self`.
214    ///
215    /// # Algorithm
216    ///
217    /// Range reduction `x = 2^k * m` with `m ∈ [1, 2)`, then a Mercator
218    /// reduction `x = 2^k * m` with `m ∈ [1, 2)`, then the
219    /// area-hyperbolic-tangent series
220    /// `ln(m) = 2·artanh(t)`, `t = (m-1)/(m+1) ∈ [0, 1/3]`,
221    /// `artanh(t) = t + t³/3 + t⁵/5 + …`, evaluated in a 256-bit
222    /// fixed-point intermediate at `SCALE + 20` working digits. The 20
223    /// guard digits bound the total accumulated rounding error far
224    /// below 0.5 ULP of the output, so the result — `k·ln(2) + ln(m)`,
225    /// rounded once at the end — is correctly rounded.
226    ///
227    /// # Precision
228    ///
229    /// Strict: integer-only, and **correctly rounded** — the result is
230    /// within 0.5 ULP of the exact natural logarithm (IEEE-754
231    /// round-to-nearest).
232    ///
233    /// # Panics
234    ///
235    /// Panics if `self <= 0`, or if the result overflows the type's
236    /// representable range (only possible for `ln` of a near-`MAX`
237    /// value at `SCALE >= 37`).
238    ///
239    /// Always available, regardless of the `strict` feature. When
240    /// `strict` is enabled, the plain [`Self::ln`] delegates here.
241    #[inline]
242    #[must_use]
243    pub fn ln_strict(self) -> Self {
244        self.ln_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
245    }
246
247    /// Natural log under the supplied rounding mode. See [`Self::ln_strict`].
248    #[inline]
249    #[must_use]
250    pub fn ln_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
251        use crate::d_w128_kernels::Fixed;
252        assert!(self.0 > 0, "D38::ln: argument must be positive");
253        let one_bits: i128 = 10_i128.pow(SCALE);
254        if self.0 == one_bits {
255            return Self::ZERO;
256        }
257        let delta = self.0 - one_bits;
258        let ln1p_band: i128 = 10_i128.pow(SCALE.saturating_sub((SCALE + 1) / 2));
259        if delta.abs() <= ln1p_band {
260            return Self::from_bits(delta);
261        }
262        // Const-folded guard so SCALE+STRICT_GUARD and 10^STRICT_GUARD
263        // resolve at compile time per tier.
264        let w = SCALE + STRICT_GUARD;
265        let v_w =
266            Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
267        let raw = ln_fixed(v_w, w)
268            .round_to_i128_with(w, SCALE, mode)
269            .expect("D38::ln: result out of range");
270        Self::from_bits(raw)
271    }
272
273    /// Natural logarithm with a caller-chosen number of guard digits
274    /// above the storage scale, trading away the strict 0.5-ULP
275    /// guarantee for proportionally faster evaluation.
276    ///
277    /// `working_digits` controls the working scale `w = SCALE +
278    /// working_digits` of the internal series evaluation. The default
279    /// `ln_strict` uses `working_digits = 30` (the same `STRICT_GUARD`
280    /// the rest of the strict family uses, sized for `<= 0.5 ULP` at
281    /// every supported `SCALE`). Callers can request fewer guard digits
282    /// to converge the Taylor series in fewer iterations:
283    ///
284    /// - `working_digits ≈ 6-10`: roughly `working_digits` digits of
285    ///   accuracy at the storage scale; typically 1.5-3× faster than
286    ///   strict; suitable for plotting, intermediate convergence
287    ///   checks, or any computation where bit-exact rounding is not
288    ///   required.
289    /// - `working_digits ≥ 30`: same accuracy as `ln_strict`, but
290    ///   slower than calling `ln_strict` directly because `w` is a
291    ///   runtime value here. Prefer `ln_strict` when you want full
292    ///   precision.
293    ///
294    /// The zero / one / linear-band fast paths fire regardless of the
295    /// requested guard — those answers are exact and don't depend on
296    /// the working precision.
297    ///
298    /// # Panics
299    ///
300    /// Same as `ln_strict`: argument must be positive.
301    #[inline]
302    #[must_use]
303    pub fn ln_approx(self, working_digits: u32) -> Self {
304        self.ln_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
305    }
306
307    /// Natural log with caller-chosen guard digits AND rounding mode.
308    /// See [`Self::ln_approx`] for accuracy/speed contract.
309    #[inline]
310    #[must_use]
311    pub fn ln_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
312        // Redirect to const-folded strict path when guard matches.
313        if working_digits == STRICT_GUARD {
314            return self.ln_strict_with(mode);
315        }
316        use crate::d_w128_kernels::Fixed;
317        assert!(self.0 > 0, "D38::ln: argument must be positive");
318        let one_bits: i128 = 10_i128.pow(SCALE);
319        if self.0 == one_bits {
320            return Self::ZERO;
321        }
322        let delta = self.0 - one_bits;
323        let ln1p_band: i128 = 10_i128.pow(SCALE.saturating_sub((SCALE + 1) / 2));
324        if delta.abs() <= ln1p_band {
325            return Self::from_bits(delta);
326        }
327        let w = SCALE + working_digits;
328        let v_w =
329            Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(working_digits));
330        let raw = ln_fixed(v_w, w)
331            .round_to_i128_with(w, SCALE, mode)
332            .expect("D38::ln: result out of range");
333        Self::from_bits(raw)
334    }
335
336    /// Returns the natural logarithm (base e) of `self`.
337    ///
338    /// With the `strict` feature enabled this is the integer-only
339    /// [`Self::ln_strict`]; without it, the f64-bridge form.
340    #[cfg(all(feature = "strict", not(feature = "fast")))]
341    #[inline]
342    #[must_use]
343    pub fn ln(self) -> Self {
344        self.ln_strict()
345    }
346
347    /// Returns the logarithm of `self` in the given `base`, computed
348    /// integer-only as `ln(self) / ln(base)` — both logarithms and the
349    /// division are carried in the wide guard-digit intermediate, so
350    /// the result is correctly rounded.
351    ///
352    /// Always available, regardless of the `strict` feature.
353    ///
354    /// # Panics
355    ///
356    /// Panics if `self <= 0` or `base <= 0`, or if `base == 1`
357    /// (division by `ln(1) = 0`).
358    #[inline]
359    #[must_use]
360    pub fn log_strict(self, base: Self) -> Self {
361        self.log_strict_with(base, crate::rounding::DEFAULT_ROUNDING_MODE)
362    }
363
364    /// Logarithm in `base` under the supplied rounding mode.
365    #[inline]
366    #[must_use]
367    pub fn log_strict_with(self, base: Self, mode: crate::rounding::RoundingMode) -> Self {
368        use crate::d_w128_kernels::Fixed;
369        assert!(self.0 > 0, "D38::log: argument must be positive");
370        assert!(base.0 > 0, "D38::log: base must be positive");
371        let w = SCALE + STRICT_GUARD;
372        let pow = 10u128.pow(STRICT_GUARD);
373        let v_w = Fixed::from_u128_mag(self.0 as u128, false).mul_u128(pow);
374        let b_w = Fixed::from_u128_mag(base.0 as u128, false).mul_u128(pow);
375        let ln_b = ln_fixed(b_w, w);
376        assert!(!ln_b.is_zero(), "D38::log: base must not equal 1 (ln(1) is zero)");
377        let raw = ln_fixed(v_w, w)
378            .div(ln_b, w)
379            .round_to_i128_with(w, SCALE, mode)
380            .expect("D38::log: result out of range");
381        Self::from_bits(raw)
382    }
383
384    /// Logarithm with caller-chosen guard digits. See `ln_approx`.
385    #[inline]
386    #[must_use]
387    pub fn log_approx(self, base: Self, working_digits: u32) -> Self {
388        self.log_approx_with(base, working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
389    }
390
391    /// Logarithm with caller-chosen guard digits AND rounding mode.
392    #[inline]
393    #[must_use]
394    pub fn log_approx_with(self, base: Self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
395        if working_digits == STRICT_GUARD {
396            return self.log_strict_with(base, mode);
397        }
398        use crate::d_w128_kernels::Fixed;
399        assert!(self.0 > 0, "D38::log: argument must be positive");
400        assert!(base.0 > 0, "D38::log: base must be positive");
401        let w = SCALE + working_digits;
402        let pow = 10u128.pow(working_digits);
403        let v_w = Fixed::from_u128_mag(self.0 as u128, false).mul_u128(pow);
404        let b_w = Fixed::from_u128_mag(base.0 as u128, false).mul_u128(pow);
405        let ln_b = ln_fixed(b_w, w);
406        assert!(!ln_b.is_zero(), "D38::log: base must not equal 1 (ln(1) is zero)");
407        let raw = ln_fixed(v_w, w)
408            .div(ln_b, w)
409            .round_to_i128_with(w, SCALE, mode)
410            .expect("D38::log: result out of range");
411        Self::from_bits(raw)
412    }
413
414    /// Returns the logarithm of `self` in the given `base`.
415    ///
416    /// With the `strict` feature enabled this is the integer-only
417    /// [`Self::log_strict`]; without it, the f64-bridge form.
418    #[cfg(all(feature = "strict", not(feature = "fast")))]
419    #[inline]
420    #[must_use]
421    pub fn log(self, base: Self) -> Self {
422        self.log_strict(base)
423    }
424
425    /// Returns the base-2 logarithm of `self`, computed integer-only as
426    /// `ln(self) / ln(2)` in the wide guard-digit intermediate — the
427    /// result is correctly rounded.
428    ///
429    /// Always available, regardless of the `strict` feature.
430    ///
431    /// # Panics
432    ///
433    /// Panics if `self <= 0`.
434    #[inline]
435    #[must_use]
436    pub fn log2_strict(self) -> Self {
437        self.log2_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
438    }
439
440    /// Base-2 log under the supplied rounding mode.
441    #[inline]
442    #[must_use]
443    pub fn log2_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
444        use crate::d_w128_kernels::Fixed;
445        assert!(self.0 > 0, "D38::log2: argument must be positive");
446        let w = SCALE + STRICT_GUARD;
447        let v_w =
448            Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
449        let raw = ln_fixed(v_w, w)
450            .div(wide_ln2(w), w)
451            .round_to_i128_with(w, SCALE, mode)
452            .expect("D38::log2: result out of range");
453        Self::from_bits(raw)
454    }
455
456    /// Base-2 log with caller-chosen guard digits.
457    #[inline]
458    #[must_use]
459    pub fn log2_approx(self, working_digits: u32) -> Self {
460        self.log2_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
461    }
462
463    /// Base-2 log with caller-chosen guard digits AND rounding mode.
464    #[inline]
465    #[must_use]
466    pub fn log2_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
467        if working_digits == STRICT_GUARD {
468            return self.log2_strict_with(mode);
469        }
470        use crate::d_w128_kernels::Fixed;
471        assert!(self.0 > 0, "D38::log2: argument must be positive");
472        let w = SCALE + working_digits;
473        let v_w =
474            Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(working_digits));
475        let raw = ln_fixed(v_w, w)
476            .div(wide_ln2(w), w)
477            .round_to_i128_with(w, SCALE, mode)
478            .expect("D38::log2: result out of range");
479        Self::from_bits(raw)
480    }
481
482    /// Returns the base-2 logarithm of `self`.
483    ///
484    /// With the `strict` feature enabled this is the integer-only
485    /// [`Self::log2_strict`]; without it, the f64-bridge form.
486    #[cfg(all(feature = "strict", not(feature = "fast")))]
487    #[inline]
488    #[must_use]
489    pub fn log2(self) -> Self {
490        self.log2_strict()
491    }
492
493    /// Returns the base-10 logarithm of `self`, computed integer-only
494    /// as `ln(self) / ln(10)` in the wide guard-digit intermediate —
495    /// the result is correctly rounded.
496    ///
497    /// Always available, regardless of the `strict` feature.
498    ///
499    /// # Panics
500    ///
501    /// Panics if `self <= 0`.
502    #[inline]
503    #[must_use]
504    pub fn log10_strict(self) -> Self {
505        self.log10_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
506    }
507
508    /// Base-10 log under the supplied rounding mode.
509    #[inline]
510    #[must_use]
511    pub fn log10_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
512        use crate::d_w128_kernels::Fixed;
513        assert!(self.0 > 0, "D38::log10: argument must be positive");
514        let w = SCALE + STRICT_GUARD;
515        let v_w =
516            Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
517        let raw = ln_fixed(v_w, w)
518            .div(wide_ln10(w), w)
519            .round_to_i128_with(w, SCALE, mode)
520            .expect("D38::log10: result out of range");
521        Self::from_bits(raw)
522    }
523
524    /// Base-10 log with caller-chosen guard digits.
525    #[inline]
526    #[must_use]
527    pub fn log10_approx(self, working_digits: u32) -> Self {
528        self.log10_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
529    }
530
531    /// Base-10 log with caller-chosen guard digits AND rounding mode.
532    #[inline]
533    #[must_use]
534    pub fn log10_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
535        if working_digits == STRICT_GUARD {
536            return self.log10_strict_with(mode);
537        }
538        use crate::d_w128_kernels::Fixed;
539        assert!(self.0 > 0, "D38::log10: argument must be positive");
540        let w = SCALE + working_digits;
541        let v_w =
542            Fixed::from_u128_mag(self.0 as u128, false).mul_u128(10u128.pow(working_digits));
543        let raw = ln_fixed(v_w, w)
544            .div(wide_ln10(w), w)
545            .round_to_i128_with(w, SCALE, mode)
546            .expect("D38::log10: result out of range");
547        Self::from_bits(raw)
548    }
549
550    /// Returns the base-10 logarithm of `self`.
551    ///
552    /// With the `strict` feature enabled this is the integer-only
553    /// [`Self::log10_strict`]; without it, the f64-bridge form.
554    #[cfg(all(feature = "strict", not(feature = "fast")))]
555    #[inline]
556    #[must_use]
557    pub fn log10(self) -> Self {
558        self.log10_strict()
559    }
560
561    // Exponentials
562
563    /// Returns `e^self` (natural exponential).
564    ///
565    /// # Algorithm
566    ///
567    /// Range reduction `x = k·ln(2) + s` with `k = round(x / ln 2)` and
568    /// `|s| ≤ ln(2)/2 ≈ 0.347`, then the Taylor series
569    /// `exp(s) = 1 + s + s²/2! + …` evaluated in a 256-bit `Fixed`
570    /// intermediate at `SCALE + 30` working digits. Reassembly is
571    /// `exp(x) = 2^k · exp(s)`, applied as a shift on the working-scale
572    /// value *before* the final rounding, so the `2^k` factor never
573    /// amplifies a rounding error. The result is rounded once,
574    /// half-to-even, back to `SCALE`.
575    ///
576    /// # Precision
577    ///
578    /// Strict: integer-only, and **correctly rounded** — the result is
579    /// within 0.5 ULP of the exact exponential (IEEE-754
580    /// round-to-nearest).
581    ///
582    /// # Panics
583    ///
584    /// Panics if the result overflows the type's representable range.
585    #[inline]
586    #[must_use]
587    pub fn exp_strict(self) -> Self {
588        self.exp_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
589    }
590
591    /// `e^self` under the supplied rounding mode.
592    #[inline]
593    #[must_use]
594    pub fn exp_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
595        use crate::d_w128_kernels::Fixed;
596        if self.0 == 0 {
597            return Self::ONE;
598        }
599        let w = SCALE + STRICT_GUARD;
600        let negative_input = self.0 < 0;
601        let v_w = Fixed::from_u128_mag(self.0.unsigned_abs(), false)
602            .mul_u128(10u128.pow(STRICT_GUARD));
603        let v_w = if negative_input { v_w.neg() } else { v_w };
604        let raw = exp_fixed(v_w, w)
605            .round_to_i128_with(w, SCALE, mode)
606            .expect("D38::exp: result overflows the representable range");
607        Self::from_bits(raw)
608    }
609
610    /// Exponential with caller-chosen guard digits.
611    #[inline]
612    #[must_use]
613    pub fn exp_approx(self, working_digits: u32) -> Self {
614        self.exp_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
615    }
616
617    /// Exponential with caller-chosen guard digits AND rounding mode.
618    #[inline]
619    #[must_use]
620    pub fn exp_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
621        if working_digits == STRICT_GUARD {
622            return self.exp_strict_with(mode);
623        }
624        use crate::d_w128_kernels::Fixed;
625        if self.0 == 0 {
626            return Self::ONE;
627        }
628        let w = SCALE + working_digits;
629        let negative_input = self.0 < 0;
630        let v_w = Fixed::from_u128_mag(self.0.unsigned_abs(), false)
631            .mul_u128(10u128.pow(working_digits));
632        let v_w = if negative_input { v_w.neg() } else { v_w };
633        let raw = exp_fixed(v_w, w)
634            .round_to_i128_with(w, SCALE, mode)
635            .expect("D38::exp: result overflows the representable range");
636        Self::from_bits(raw)
637    }
638
639    /// Returns `e^self` (natural exponential).
640    ///
641    /// With the `strict` feature enabled this is the integer-only
642    /// [`Self::exp_strict`]; without it, the f64-bridge form.
643    #[cfg(all(feature = "strict", not(feature = "fast")))]
644    #[inline]
645    #[must_use]
646    pub fn exp(self) -> Self {
647        self.exp_strict()
648    }
649
650    /// Returns `2^self` (base-2 exponential), computed integer-only as
651    /// `exp(self · ln(2))` — the `self · ln(2)` product is formed in
652    /// the wide guard-digit intermediate (not at the type's own scale),
653    /// so the result is correctly rounded.
654    ///
655    /// Always available, regardless of the `strict` feature.
656    ///
657    /// # Panics
658    ///
659    /// Panics if the result overflows D38's representable range.
660    #[inline]
661    #[must_use]
662    pub fn exp2_strict(self) -> Self {
663        self.exp2_strict_with(crate::rounding::DEFAULT_ROUNDING_MODE)
664    }
665
666    /// `2^self` under the supplied rounding mode.
667    #[inline]
668    #[must_use]
669    pub fn exp2_strict_with(self, mode: crate::rounding::RoundingMode) -> Self {
670        use crate::d_w128_kernels::Fixed;
671        if self.0 == 0 {
672            return Self::ONE;
673        }
674        let w = SCALE + STRICT_GUARD;
675        let negative_input = self.0 < 0;
676        let v_w = Fixed::from_u128_mag(self.0.unsigned_abs(), false)
677            .mul_u128(10u128.pow(STRICT_GUARD));
678        let v_w = if negative_input { v_w.neg() } else { v_w };
679        let arg_w = v_w.mul(wide_ln2(w), w);
680        let raw = exp_fixed(arg_w, w)
681            .round_to_i128_with(w, SCALE, mode)
682            .expect("D38::exp2: result overflows the representable range");
683        Self::from_bits(raw)
684    }
685
686    /// Base-2 exponential with caller-chosen guard digits.
687    #[inline]
688    #[must_use]
689    pub fn exp2_approx(self, working_digits: u32) -> Self {
690        self.exp2_approx_with(working_digits, crate::rounding::DEFAULT_ROUNDING_MODE)
691    }
692
693    /// Base-2 exponential with caller-chosen guard digits AND rounding mode.
694    #[inline]
695    #[must_use]
696    pub fn exp2_approx_with(self, working_digits: u32, mode: crate::rounding::RoundingMode) -> Self {
697        if working_digits == STRICT_GUARD {
698            return self.exp2_strict_with(mode);
699        }
700        use crate::d_w128_kernels::Fixed;
701        if self.0 == 0 {
702            return Self::ONE;
703        }
704        let w = SCALE + working_digits;
705        let negative_input = self.0 < 0;
706        let v_w = Fixed::from_u128_mag(self.0.unsigned_abs(), false)
707            .mul_u128(10u128.pow(working_digits));
708        let v_w = if negative_input { v_w.neg() } else { v_w };
709        let arg_w = v_w.mul(wide_ln2(w), w);
710        let raw = exp_fixed(arg_w, w)
711            .round_to_i128_with(w, SCALE, mode)
712            .expect("D38::exp2: result overflows the representable range");
713        Self::from_bits(raw)
714    }
715
716    /// Returns `2^self` (base-2 exponential).
717    ///
718    /// With the `strict` feature enabled this is the integer-only
719    /// [`Self::exp2_strict`]; without it, the f64-bridge form.
720    #[cfg(all(feature = "strict", not(feature = "fast")))]
721    #[inline]
722    #[must_use]
723    pub fn exp2(self) -> Self {
724        self.exp2_strict()
725    }
726}
727
728
729
730#[cfg(all(test, feature = "strict", not(feature = "fast")))]
731mod strict_tests {
732    use crate::core_type::D38s12;
733
734    /// Tolerance in ULPs for the strict transcendentals. They are
735    /// correctly rounded (≤ 0.5 ULP); 2 LSB of slack absorbs the
736    /// test's own expected-value rounding.
737    const STRICT_TOLERANCE_LSB: i128 = 2;
738
739    fn within(actual: D38s12, expected_bits: i128, tolerance: i128) -> bool {
740        (actual.to_bits() - expected_bits).abs() <= tolerance
741    }
742
743    /// ln(1) == 0 exactly (no series terms contribute).
744    #[test]
745    fn ln_of_one_is_zero() {
746        assert_eq!(D38s12::ONE.ln(), D38s12::ZERO);
747    }
748
749    /// `ln_strict` is correctly rounded: cross-check against the f64
750    /// bridge at a scale where `f64` (≈ 15–16 significant digits) is
751    /// comfortably more precise than the type's ULP, so the
752    /// correctly-rounded integer result must agree to within 1 ULP.
753    #[test]
754    fn ln_strict_is_correctly_rounded_vs_f64() {
755        use crate::core_type::D38;
756        // D38<9>: ULP is 1e-9; f64 ln is good to ~1e-15 over this
757        // range, so the correctly-rounded result is within 1 ULP of the
758        // f64 reference (allow 1 for the f64 reference's own rounding).
759        fn check(raw: i128) {
760            let x = D38::<9>::from_bits(raw);
761            let strict = x.ln_strict().to_bits();
762            let reference = {
763                let v = raw as f64 / 1e9;
764                (v.ln() * 1e9).round() as i128
765            };
766            assert!(
767                (strict - reference).abs() <= 1,
768                "ln_strict({raw}) = {strict}, f64 reference {reference}"
769            );
770        }
771        for &raw in &[
772            1,
773            500_000_000,            // 0.5
774            1_000_000_000,          // 1.0
775            1_500_000_000,          // 1.5
776            2_000_000_000,          // 2.0
777            2_718_281_828,          // ≈ e
778            10_000_000_000,         // 10
779            123_456_789_012_345,    // ≈ 123456.78…
780            999_999_999_999_999_999,// ≈ 1e9
781            i64::MAX as i128,
782        ] {
783            check(raw);
784        }
785    }
786
787    /// `exp_strict` / `log2_strict` / `log10_strict` agree with the f64
788    /// bridge to within 1 ULP at D38<9>, where f64 is comfortably more
789    /// precise than the type's ULP — strong evidence of correct
790    /// rounding for the whole log/exp family.
791    #[test]
792    fn strict_log_exp_family_matches_f64() {
793        use crate::core_type::D38;
794        fn check_exp(raw: i128) {
795            let x = D38::<9>::from_bits(raw);
796            let strict = x.exp_strict().to_bits();
797            let reference = ((raw as f64 / 1e9).exp() * 1e9).round() as i128;
798            assert!(
799                (strict - reference).abs() <= 1,
800                "exp_strict({raw}) = {strict}, f64 reference {reference}"
801            );
802        }
803        fn check_log2(raw: i128) {
804            let x = D38::<9>::from_bits(raw);
805            let strict = x.log2_strict().to_bits();
806            let reference = ((raw as f64 / 1e9).log2() * 1e9).round() as i128;
807            assert!(
808                (strict - reference).abs() <= 1,
809                "log2_strict({raw}) = {strict}, f64 reference {reference}"
810            );
811        }
812        fn check_log10(raw: i128) {
813            let x = D38::<9>::from_bits(raw);
814            let strict = x.log10_strict().to_bits();
815            let reference = ((raw as f64 / 1e9).log10() * 1e9).round() as i128;
816            assert!(
817                (strict - reference).abs() <= 1,
818                "log10_strict({raw}) = {strict}, f64 reference {reference}"
819            );
820        }
821        // exp: keep the argument modest so the result stays in range.
822        for &raw in &[
823            -5_000_000_000, -1_000_000_000, -500_000_000, 1, 500_000_000,
824            1_000_000_000, 2_000_000_000, 5_000_000_000, 10_000_000_000,
825        ] {
826            check_exp(raw);
827        }
828        // log2 / log10: positive arguments across the range.
829        for &raw in &[
830            1, 500_000_000, 1_000_000_000, 2_000_000_000, 8_000_000_000,
831            10_000_000_000, 123_456_789_012_345, i64::MAX as i128,
832        ] {
833            check_log2(raw);
834            check_log10(raw);
835        }
836    }
837
838    /// `exp2_strict` is exact at integer arguments: `2^10` is `1024`.
839    #[test]
840    fn strict_exp2_at_integers() {
841        use crate::core_type::D38;
842        for k in 0_i128..=12 {
843            let x = D38::<12>::from_bits(k * 10i128.pow(12));
844            let got = x.exp2_strict().to_bits();
845            let expected = (1i128 << k) * 10i128.pow(12);
846            // Correctly rounded: exactly the integer power of two.
847            assert_eq!(got, expected, "2^{k}");
848        }
849    }
850
851    /// `ln_strict` is exact at the powers of two it can represent:
852    /// `ln(2^k)` rounds to `k · ln(2)` at the type's scale.
853    #[test]
854    fn ln_strict_of_powers_of_two() {
855        use crate::core_type::D38;
856        // ln(2) at scale 18, correctly rounded:
857        // 0.693147180559945309… -> 693147180559945309.
858        let ln2_s18: i128 = 693_147_180_559_945_309;
859        for k in 1_i128..=20 {
860            let x = D38::<18>::from_bits((1i128 << k) * 10i128.pow(18));
861            let got = x.ln_strict().to_bits();
862            let expected = k * ln2_s18;
863            // k·ln(2) accumulates k roundings of the scale-18 ln(2);
864            // the correctly-rounded result is within ⌈k/2⌉+1 of the
865            // naive k·(rounded ln2).
866            let tol = k / 2 + 2;
867            assert!(
868                (got - expected).abs() <= tol,
869                "ln(2^{k}) = {got}, expected ≈ {expected}"
870            );
871        }
872    }
873
874    /// ln(2) at scale 12 = 693_147_180_560 (canonical rounded to 12 places).
875    #[test]
876    fn ln_of_two_close_to_canonical() {
877        let two = D38s12::from_bits(2_000_000_000_000);
878        let result = two.ln();
879        // ln(2) = 0.693147180559945... so at scale 12, bits = 693_147_180_560.
880        assert!(
881            within(result, 693_147_180_560, STRICT_TOLERANCE_LSB),
882            "ln(2) bits = {}",
883            result.to_bits()
884        );
885    }
886
887    /// ln(e) is approximately 1. Uses the existing pi/e constants via DecimalConsts.
888    #[test]
889    fn ln_of_e_close_to_one() {
890        // e at scale 12 = 2_718_281_828_459 (canonical 35-digit reference rescaled).
891        let e_at_s12 = D38s12::from_bits(2_718_281_828_459);
892        let result = e_at_s12.ln();
893        // ln(e) = 1.0 -> bits = 1_000_000_000_000 at scale 12.
894        assert!(
895            within(result, 1_000_000_000_000, STRICT_TOLERANCE_LSB),
896            "ln(e) bits = {}, expected ~1_000_000_000_000",
897            result.to_bits()
898        );
899    }
900
901    /// ln(10) at scale 12 = 2_302_585_092_994 (canonical).
902    #[test]
903    fn ln_of_ten_close_to_canonical() {
904        let ten = D38s12::from_bits(10_000_000_000_000);
905        let result = ten.ln();
906        assert!(
907            within(result, 2_302_585_092_994, STRICT_TOLERANCE_LSB),
908            "ln(10) bits = {}, expected ~2_302_585_092_994",
909            result.to_bits()
910        );
911    }
912
913    /// ln of a value > 1 is positive.
914    #[test]
915    fn ln_above_one_is_positive() {
916        let v = D38s12::from_bits(1_500_000_000_000); // 1.5
917        let result = v.ln();
918        assert!(result.to_bits() > 0);
919    }
920
921    /// ln of a value in (0, 1) is negative.
922    #[test]
923    fn ln_below_one_is_negative() {
924        let v = D38s12::from_bits(500_000_000_000); // 0.5
925        let result = v.ln();
926        assert!(result.to_bits() < 0);
927        // ln(0.5) = -ln(2) ~= -0.693147...
928        assert!(
929            within(result, -693_147_180_560, STRICT_TOLERANCE_LSB),
930            "ln(0.5) bits = {}, expected ~-693_147_180_560",
931            result.to_bits()
932        );
933    }
934
935    #[test]
936    #[should_panic(expected = "argument must be positive")]
937    fn ln_of_zero_panics() {
938        let _ = D38s12::ZERO.ln();
939    }
940
941    #[test]
942    #[should_panic(expected = "argument must be positive")]
943    fn ln_of_negative_panics() {
944        let neg = D38s12::from_bits(-1_000_000_000_000);
945        let _ = neg.ln();
946    }
947
948    // log2 / log10 / log derive from ln; tolerance grows because the
949    // additional division step accumulates ~1 LSB.
950    const DERIVED_LOG_TOLERANCE_LSB: i128 = 20;
951
952    /// log2(2) ~= 1.
953    #[test]
954    fn log2_of_two_is_one() {
955        let two = D38s12::from_bits(2_000_000_000_000);
956        let result = two.log2();
957        assert!(
958            within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
959            "log2(2) bits = {}",
960            result.to_bits()
961        );
962    }
963
964    /// log2(8) ~= 3.
965    #[test]
966    fn log2_of_eight_is_three() {
967        let eight = D38s12::from_bits(8_000_000_000_000);
968        let result = eight.log2();
969        assert!(
970            within(result, 3_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
971            "log2(8) bits = {}",
972            result.to_bits()
973        );
974    }
975
976    /// log10(10) ~= 1.
977    #[test]
978    fn log10_of_ten_is_one() {
979        let ten = D38s12::from_bits(10_000_000_000_000);
980        let result = ten.log10();
981        assert!(
982            within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
983            "log10(10) bits = {}",
984            result.to_bits()
985        );
986    }
987
988    /// log10(100) ~= 2.
989    #[test]
990    fn log10_of_hundred_is_two() {
991        let hundred = D38s12::from_bits(100_000_000_000_000);
992        let result = hundred.log10();
993        assert!(
994            within(result, 2_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
995            "log10(100) bits = {}",
996            result.to_bits()
997        );
998    }
999
1000    /// log_base_b(b) == 1 for any b > 0, b != 1.
1001    #[test]
1002    fn log_self_is_one() {
1003        let base = D38s12::from_bits(5_000_000_000_000); // 5
1004        let result = base.log(base);
1005        assert!(
1006            within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
1007            "log_5(5) bits = {}",
1008            result.to_bits()
1009        );
1010    }
1011
1012    /// log_2(8) == 3 via the generic log.
1013    #[test]
1014    fn log_with_base_two() {
1015        let eight = D38s12::from_bits(8_000_000_000_000);
1016        let two = D38s12::from_bits(2_000_000_000_000);
1017        let result = eight.log(two);
1018        assert!(
1019            within(result, 3_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
1020            "log_2(8) bits = {}",
1021            result.to_bits()
1022        );
1023    }
1024
1025    #[test]
1026    #[should_panic(expected = "base must not equal 1")]
1027    fn log_base_one_panics() {
1028        let x = D38s12::from_bits(5_000_000_000_000);
1029        let one = D38s12::ONE;
1030        let _ = x.log(one);
1031    }
1032
1033    // exp / exp2: tolerance accounts for Taylor truncation, 2^k bit-shift
1034    // exactness, and the range-reduction rounding step. ~20 LSB at D38s12.
1035    const EXP_TOLERANCE_LSB: i128 = 20;
1036
1037    /// exp(0) == 1 exactly.
1038    #[test]
1039    fn exp_of_zero_is_one() {
1040        assert_eq!(D38s12::ZERO.exp(), D38s12::ONE);
1041    }
1042
1043    /// exp(1) ~= e.
1044    #[test]
1045    fn exp_of_one_is_e() {
1046        let result = D38s12::ONE.exp();
1047        // e ~= 2.718281828459 at D38s12.
1048        assert!(
1049            within(result, 2_718_281_828_459, EXP_TOLERANCE_LSB),
1050            "exp(1) bits = {}",
1051            result.to_bits()
1052        );
1053    }
1054
1055    /// exp(ln(2)) ~= 2.
1056    #[test]
1057    fn exp_of_ln_2_is_two() {
1058        let ln_2 = D38s12::from_bits(693_147_180_560);
1059        let result = ln_2.exp();
1060        assert!(
1061            within(result, 2_000_000_000_000, EXP_TOLERANCE_LSB),
1062            "exp(ln 2) bits = {}",
1063            result.to_bits()
1064        );
1065    }
1066
1067    /// exp(-1) ~= 1/e ~= 0.367879441171.
1068    #[test]
1069    fn exp_of_negative_one_is_reciprocal_e() {
1070        let neg_one = D38s12::from_bits(-1_000_000_000_000);
1071        let result = neg_one.exp();
1072        // 1/e ~= 0.367879441171 at D38s12 -> bits ~= 367_879_441_171.
1073        assert!(
1074            within(result, 367_879_441_171, EXP_TOLERANCE_LSB),
1075            "exp(-1) bits = {}",
1076            result.to_bits()
1077        );
1078    }
1079
1080    /// exp2(0) == 1 exactly.
1081    #[test]
1082    fn exp2_of_zero_is_one() {
1083        assert_eq!(D38s12::ZERO.exp2(), D38s12::ONE);
1084    }
1085
1086    /// exp2(1) ~= 2.
1087    #[test]
1088    fn exp2_of_one_is_two() {
1089        let result = D38s12::ONE.exp2();
1090        assert!(
1091            within(result, 2_000_000_000_000, EXP_TOLERANCE_LSB),
1092            "exp2(1) bits = {}",
1093            result.to_bits()
1094        );
1095    }
1096
1097    /// exp2(10) ~= 1024.
1098    #[test]
1099    fn exp2_of_ten_is_1024() {
1100        let ten = D38s12::from_bits(10_000_000_000_000);
1101        let result = ten.exp2();
1102        assert!(
1103            within(result, 1_024_000_000_000_000, EXP_TOLERANCE_LSB * 10),
1104            "exp2(10) bits = {}",
1105            result.to_bits()
1106        );
1107    }
1108}
1109