decimal-scaled 0.5.0

Const-generic base-10 fixed-point decimals (D18/D38/D76/D153/D307 and the half-width tiers up to D1232) with integer-only transcendentals correctly rounded to within 0.5 ULP — exact at the type's last representable place. Deterministic across every platform; no_std-friendly.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
// SPDX-FileCopyrightText: 2026 John Moxley
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Natural-logarithm series kernel — `ln_fixed` evaluated on the
//! 256-bit `Fixed` guard-digit intermediate with a configurable
//! working-scale guard.
//!
//! The narrow `Int<2>`-storage series path: it serves the narrow
//! D18 / D38 tier, evaluating the log series in the wider `Fixed`
//! intermediate because the narrow storage cannot host the guard
//! digits a correctly-rounded result needs. Captures the four-variant
//! matrix entry shape (`strict` vs `approx`, each with an
//! explicit-rounding-mode sibling) as a single kernel parameterised by
//! `working_digits`.
//!
//! Hosts the shared `Fixed` ln primitives used by every D38 strict-
//! ln callsite plus the `policy::ln` defaults — `STRICT_GUARD`,
//! `ln_fixed`, `wide_ln2`, `wide_ln10` — so the typed-shell file
//! has no `crate::algos::*` or `crate::algos::support::fixed::*` references
//! left.
//!
//! Fast paths preserved verbatim from the typed surface:
//! - `self == 10^SCALE` (i.e. logical 1.0) returns `Self::ZERO`.
//! - `|self - 1| <= 10^(SCALE - ceil(SCALE/2))` returns `(self - 1)`
//!   directly — the linear `ln(1+x) ≈ x` band where the result is
//!   exact at storage precision.
//!
//! Panics on `raw <= 0` (the typed method's contract).

use crate::algos::exp::exp_generic as eg;
use crate::algos::support::fixed::Fixed;
use crate::algos::support::narrow_ziv::{self, WZiv};
use crate::int::types::Int;
use crate::support::rounding::{RoundingMode, is_nearest_mode};

/// Guard digits added below the storage scale for the D38 strict log
/// family. The 256-bit `Fixed` intermediate runs at
/// `w = SCALE + STRICT_GUARD`; with `SCALE <= 38` that caps `w` at 68
/// digits, comfortably inside the 75-digit window the embedded
/// `ln 2` / `ln 10` constants cover.
pub(crate) const STRICT_GUARD: u32 = 30;

// `ln(2)` and `ln(10)` are embedded at 75 fractional digits — the same
// reference scale `wide_pi` uses — so callers running at the maximum
// strict working scale `W = SCALE + STRICT_GUARD = 38 + 30 = 68` always
// rescale **down** (never up, which would wrap `from_w − to_w` as `u32`
// and silently produce a wrong constant). The 75-digit window covers
// every supported strict scale; widening it further is wasted work
// because the working scale is capped by `D38<SCALE>`'s storage range
// (no input that fits `i128` at `SCALE > 38` exists).
//
// Half-to-even rounded ln(2) × 10^75 and ln(10) × 10^75; both fit an
// `Int<4>` (max ≈ 5.78 × 10⁷⁶). The next-digit rounding is documented
// in-line so the truncation step is auditable from this file alone.

/// Repacks an `Int<4>` (internally `[u64; 4]`) into a
/// `Fixed` magnitude (`[u128; 2]`) sourced at scale `75`.
#[inline]
fn fixed_from_int256(raw: Int<4>) -> Fixed {
    let words = raw.limbs_le();
    Fixed {
        negative: false,
        mag: [
            (words[0] as u128) | ((words[1] as u128) << 64),
            (words[2] as u128) | ((words[3] as u128) << 64),
        ],
    }
}

/// `ln(2)` as a `Fixed` at working scale `w` (`w <= 75`). Sourced from
/// the 75-digit reference and rescaled **down** to `w`.
///
/// Caller-side precondition: `w <= 75`. The D38 strict log family runs
/// at `w = SCALE + STRICT_GUARD`, capped at `38 + 30 = 68`, so every
/// strict call site is comfortably inside the bound. A debug-assert
/// documents the invariant for any future caller.
pub(crate) fn wide_ln2(w: u32) -> Fixed {
    debug_assert!(w <= 75, "wide_ln2: working scale {w} exceeds Fixed capacity");
    fixed_from_int256(crate::consts::ln2_const_n::<4>(
        w,
        crate::support::rounding::RoundingMode::HalfToEven,
    ))
}

/// `ln(10)` as a `Fixed` at working scale `w` (`w <= 75`). Sourced from
/// the 75-digit reference and rescaled **down** to `w`.
///
/// Caller-side precondition: `w <= 75`. See [`wide_ln2`].
pub(crate) fn wide_ln10(w: u32) -> Fixed {
    debug_assert!(w <= 75, "wide_ln10: working scale {w} exceeds Fixed capacity");
    fixed_from_int256(crate::consts::ln10_const_n::<4>(
        w,
        crate::support::rounding::RoundingMode::HalfToEven,
    ))
}

/// Natural logarithm of a positive working-scale value `v_w`, returned
/// at the same working scale `w`.
///
/// Range-reduces `v = 2^k · m` with `m ∈ [1,2)` — the mantissa is
/// recomputed exactly from `v_w` once `k` is known — then evaluates
/// `ln(m) = 2·artanh((m-1)/(m+1))` (`t ∈ [0,1/3]`, fast convergence)
/// and returns `k·ln(2) + ln(m)`.
pub(crate) fn ln_fixed(v_w: Fixed, w: u32) -> Fixed {
    let one_w = Fixed {
        negative: false,
        mag: Fixed::pow10(w),
    };
    let two_w = one_w.double();

    // Range reduction: find k with v ∈ [2^k, 2^(k+1)); m_w = v_w / 2^k.
    let mut k: i32 = v_w.bit_length() as i32 - one_w.bit_length() as i32;
    let m_w = loop {
        let m = if k >= 0 {
            v_w.shr(k as u32)
        } else {
            v_w.shl((-k) as u32)
        };
        if m.ge_mag(two_w) {
            k += 1;
        } else if !m.ge_mag(one_w) {
            k -= 1;
        } else {
            break m;
        }
    };

    // t = (m - 1) / (m + 1) ∈ [0, 1/3]; artanh(t) = t + t³/3 + t⁵/5 + …
    let t = m_w.sub(one_w).div(m_w.add(one_w), w);
    let t2 = t.mul(t, w);
    let mut sum = t;
    let mut term = t;
    let mut j: u128 = 1;
    loop {
        term = term.mul(t2, w);
        let contrib = term.div_small(2 * j + 1);
        if contrib.is_zero() {
            break;
        }
        sum = sum.add(contrib);
        j += 1;
        if j > 400 {
            break;
        }
    }
    let ln_m = sum.double();

    let ln2 = wide_ln2(w);
    let k_ln2 = if k >= 0 {
        ln2.mul_u128(k as u128)
    } else {
        ln2.mul_u128((-k) as u128).neg()
    };
    k_ln2.add(ln_m)
}

/// D38 natural log with explicit `working_digits` and rounding mode.
/// Called by both `ln_strict_with` (with `working_digits = STRICT_GUARD`)
/// and `ln_approx_with` (with the caller's value).
///
/// Returns the raw `i128` storage at the input's scale; `None` when the
/// correctly-rounded result does not fit the storage (the policy wrapper
/// panics / the `checked_` surface propagates).
#[inline]
#[must_use]
pub(crate) fn ln_with(
    raw: Int<2>,
    scale: u32,
    working_digits: u32,
    mode: RoundingMode,
) -> Option<Int<2>> {
    ln_with_raw(raw.as_i128(), scale, working_digits, mode).map(Int::<2>::from_i128)
}

/// `i128` core of [`ln_with`].
#[inline]
fn ln_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> Option<i128> {
    assert!(raw > 0, "ln kernel: argument must be positive");
    let one_bits: i128 = 10_i128.pow(scale);
    if raw == one_bits {
        return Some(0);
    }
    let delta = raw - one_bits;
    let ln1p_band: i128 = 10_i128.pow(scale.saturating_sub(1) / 2);
    if delta.abs() <= ln1p_band && is_nearest_mode(mode) {
        // ln(1 + δ/10^S)·10^S = δ − δ²/(2·10^S) + … . The leading omitted term
        // is δ²/(2·10^S); at the band edge |δ| = 10^k it equals 10^(2k−S)/2, so
        // the linear value `δ` is the nearest-rounded result only while
        // 2k − S < 0 STRICTLY (the term is below half an LSB and the round is
        // not a tie). The band exponent k = ⌊(S−1)/2⌋ gives 2k − S ≤ −1, i.e.
        // ≤ 0.05 LSB — strictly clear of the half-ULP tie for EVERY S. The old
        // k = ⌊S/2⌋ put 2k − S = 0 for even S, so the edge term was exactly
        // 0.5 LSB and `δ` misrounded the tie (ln(0.999) at s6/s18/s28). Directed
        // modes need the true residual sign (the value sits sub-LSB to one side
        // of `δ`), so they fall through to the full working-scale kernel below.
        return Some(delta);
    }
    let w = scale + working_digits;
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(working_digits));
    ln_fixed(v_w, w).round_to_i128_with(w, scale, mode)
}

/// Strict variant — fixed to `STRICT_GUARD` working digits. Equivalent
/// to `ln_with(raw, scale, STRICT_GUARD, mode)` but keeps the working
/// scale `w = SCALE + STRICT_GUARD` const-folded so LLVM specialises
/// one optimal kernel per `SCALE`.
#[inline]
#[must_use]
pub(crate) fn ln_strict<const SCALE: u32>(raw: Int<2>, mode: RoundingMode) -> Option<Int<2>> {
    ln_strict_raw::<SCALE>(raw.as_i128(), mode).map(Int::<2>::from_i128)
}

/// `i128` core of [`ln_strict`].
#[inline]
fn ln_strict_raw<const SCALE: u32>(raw: i128, mode: RoundingMode) -> Option<i128> {
    assert!(raw > 0, "ln kernel: argument must be positive");
    let one_bits: i128 = 10_i128.pow(SCALE);
    if raw == one_bits {
        return Some(0);
    }
    let delta = raw - one_bits;
    let ln1p_band: i128 = 10_i128.pow(SCALE.saturating_sub(1) / 2);
    if delta.abs() <= ln1p_band && is_nearest_mode(mode) {
        // See `ln_with_raw`: the band exponent ⌊(S−1)/2⌋ keeps the omitted
        // quadratic term ≤ 0.05 LSB — strictly clear of the half-ULP tie for
        // every S (a ⌊S/2⌋ band hits exactly 0.5 LSB at the edge for even S).
        // The linear approximation `δ` is then the correctly nearest-rounded
        // result inside the band; directed modes need the residual sign and
        // fall through to the full kernel.
        return Some(delta);
    }
    let w = SCALE + STRICT_GUARD;
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
    let lv = ln_fixed(v_w, w);
    match lv.round_to_i128_clear_of_tie(w, SCALE, mode) {
        Some(r) => r,
        // Near-tie: the directed deep-near-1 family (`ln(1 ± k·10^-S)`
        // leaves the `δ²/2` deviation below the fixed working scale once
        // `δ² < 2·10^(S−30)`) and any nearest near-half — escalate. The
        // walker resolves every constructible member (deviation depth
        // ≤ 2·38 = 76 ≪ the `WZiv` reach).
        None => narrow_ziv::walk_checked(
            lv.round_to_i128_with(w, SCALE, mode),
            STRICT_GUARD,
            SCALE,
            mode,
            |g| ln_ziv(raw, SCALE, g),
        ),
    }
}

/// One `WZiv` ln probe at working scale `scale + g`.
fn ln_ziv(raw: i128, scale: u32, g: u32) -> WZiv {
    let w = scale + g;
    eg::ln_fixed::<WZiv>(narrow_ziv::lift(raw, g), w, narrow_ziv::ln2_w(w))
}

/// One `WZiv` `ln(x)/ln(b)` ratio probe at working scale `scale + g`.
/// `b_num2` selects the base: positive = the storage `b_raw` (general
/// log), `-2` / `-10` = the constant bases (log2 / log10, whose scaled
/// storage form can overflow `i128` at the maximal scale).
fn log_ratio_ziv(x_raw: i128, b_sel: i128, scale: u32, g: u32) -> WZiv {
    let w = scale + g;
    let ln2 = narrow_ziv::ln2_w(w);
    let lx = eg::ln_fixed::<WZiv>(narrow_ziv::lift(x_raw, g), w, ln2);
    let lb = match b_sel {
        -2 => ln2,
        -10 => narrow_ziv::ln10_w(w),
        b_raw => eg::ln_fixed::<WZiv>(narrow_ziv::lift(b_raw, g), w, ln2),
    };
    eg::div::<WZiv>(lx, lb, w)
}

/// Greatest common divisor (Euclid) on `u128`.
pub(crate) fn gcd_u128(mut a: u128, mut b: u128) -> u128 {
    while b != 0 {
        let t = a % b;
        a = b;
        b = t;
    }
    a
}

/// `base^e` in [`WZiv`], `None` when the result would exceed the bounded
/// bit budget (1500 bits — inside `Int<24>`'s 1536 with sign headroom).
/// Square-and-multiply; the bit-length pre-check bounds every
/// intermediate, so no step can overflow.
pub(crate) fn pow_bounded(base: u128, e: u128) -> Option<WZiv> {
    if base == 0 {
        return None; // domain-asserted positive inputs; 0 never verifies
    }
    let bl = (128 - base.leading_zeros()) as u128;
    // Saturating: `e` can be as large as `10^scale` (an irreducible
    // candidate denominator), so the budget product must not wrap.
    if bl.saturating_mul(e) > 1500 {
        return None;
    }
    let mut acc = WZiv::from_i128(1);
    let mut b = WZiv::from_u128(base);
    let mut k = e;
    while k > 0 {
        if k & 1 == 1 {
            acc = acc * b;
        }
        k >>= 1;
        if k > 0 {
            b = b * b;
        }
    }
    Some(acc)
}

/// Exact rational-power pin for the strict log near-tie terminal.
///
/// `num2` is the boundary CANDIDATE in half-ULPs at `scale` (the working
/// ratio doubled and rounded to the nearest integer): even = a grid
/// candidate (a directed near-grid tie, e.g. `log_4(8) = 3/2` on the
/// scale-19 grid), odd = a half candidate (a nearest near-half tie, e.g.
/// `log_4(32) = 5/2` at scale 0). Verifies `log_(bn/bd)(xn/xd) ==
/// num2/(2·10^scale)` EXACTLY via the integer identity `x^q == b^p`
/// (`p/q` = the candidate in lowest terms; `pow_bounded` caps the
/// verification at sizes [`WZiv`] holds — an unverifiable candidate
/// defers to the walker). On a verified exact value the result is
/// decided by exact integer arithmetic: a grid candidate IS the result;
/// a half candidate applies the mode's tie rule. This is what makes the
/// constructible exact-rational families deterministic — the working
/// residual at any finite scale is kernel noise around the boundary,
/// which no escalation can settle.
fn log_rational_pow_pin(
    xn: u128,
    xd: u128,
    bn: u128,
    bd: u128,
    scale: u32,
    num2: i128,
    mode: RoundingMode,
) -> Option<i128> {
    if num2 == 0 {
        return None; // r = 0 ⇔ x == 1, pinned upstream
    }
    let den: u128 = 2 * 10u128.pow(scale);
    let neg = num2 < 0;
    let num = num2.unsigned_abs();
    let g = gcd_u128(num, den);
    let p = num / g;
    let q = den / g;
    // log_b(x) = ±p/q ⇔ x^q == b^(±p): for the negative sign the base
    // fraction inverts.
    let (tn, td) = if neg { (bd, bn) } else { (bn, bd) };
    let lx_n = pow_bounded(xn, q)?;
    let lx_d = pow_bounded(xd, q)?;
    let rb_n = pow_bounded(tn, p)?;
    let rb_d = pow_bounded(td, p)?;
    if lx_n != rb_n || lx_d != rb_d {
        return None;
    }
    // Exact value num2/(2·10^scale): fold the half-ULP form to storage.
    let q_mag = num / 2;
    if num & 1 == 0 {
        return Some(if neg { -(q_mag as i128) } else { q_mag as i128 });
    }
    // Exactly on the half between q_mag and q_mag + 1 (magnitude side):
    // the mode's tie rule, by exact integer arithmetic.
    let bump = crate::support::rounding::should_bump(
        mode,
        core::cmp::Ordering::Equal,
        q_mag & 1 == 1,
        !neg,
    );
    let m = (q_mag + u128::from(bump)) as i128;
    Some(if neg { -m } else { m })
}

/// Reduces `a / b` to lowest terms.
pub(crate) fn reduce_fraction(a: u128, b: u128) -> (u128, u128) {
    let g = gcd_u128(a, b);
    (a / g, b / g)
}

// ── log / log2 / log10 kernels (D38, Fixed fallback) ──────────────

/// Exact-integer-logarithm pin for the D38 log family.
///
/// Returns `Some(k · 10^scale)` (the exact storage representation of the
/// integer `k`) when `value == base^k` exactly at the storage scale, i.e.
/// when the true `log_base(value)` is the exact integer `k`. Off this
/// allow-list the logarithm is irrational (Lindemann–Weierstrass), so the
/// residual is genuinely non-zero and the caller's working-scale kernel
/// runs. Pinning the exact points stops the `ln(value)/ln(base)` round-off
/// from landing a hair off the storage grid line and bumping by one LSB
/// under a directed mode (`Trunc`/`Floor`/`Ceiling`).
///
/// `value_raw` is the storage integer (`x · 10^scale`); `base_int` is
/// the *integer* base (`2` for `log2`, `10` for `log10`, or the integer
/// part of a general `log` base — pass `0` for a non-integer base, which
/// never has an exact integer power). Passing `base_int` rather than the
/// scaled `base · 10^scale` avoids overflowing `i128` when forming
/// `10^(scale+1)` at the maximal scale (the D38 max-scale ln panic).
///
/// The candidate `k` is derived by the caller from the nearest-rounded
/// result. All arithmetic is `i128`; an intermediate that overflows
/// `i128` cannot be a representable exact power, so the check returns
/// `None`.
#[inline]
fn log_exact_int_pin(value_raw: i128, base_int: i128, scale: u32, k: i128) -> Option<i128> {
    let one_s = 10i128.checked_pow(scale)?;
    if k == 0 {
        return (value_raw == one_s).then_some(0);
    }
    // A non-integer base (only the near-1 ill-conditioning probes hit
    // this) raised to an integer `k` is not an integer matching `value`
    // at storage scale, so no exact pin applies.
    if base_int == 0 {
        return None;
    }
    let kk = k.unsigned_abs();
    let exact = if k > 0 {
        // value == base^|k|: compare `base_int^|k|` against the integer
        // part of `value`, requiring `value` to be that exact integer.
        if value_raw % one_s != 0 {
            return None;
        }
        let value_int = value_raw / one_s;
        let mut pow: i128 = 1;
        let mut ok = true;
        for _ in 0..kk {
            match pow.checked_mul(base_int) {
                Some(p) => pow = p,
                None => {
                    ok = false;
                    break;
                }
            }
        }
        ok && pow == value_int
    } else {
        // value == 1 / base^|k|: `value · base^|k| == 1` exactly, i.e.
        // `value_raw · base_int^|k| == 10^scale`. Drive `value_raw` up by
        // `base_int` each step (staying bounded near `10^scale`) and
        // require exact divisibility is not needed — multiplication is
        // exact integer; the product must hit `one_s` precisely.
        let mut cur = value_raw;
        let mut ok = true;
        for _ in 0..kk {
            match cur.checked_mul(base_int) {
                Some(p) => cur = p,
                None => {
                    ok = false;
                    break;
                }
            }
        }
        ok && cur == one_s
    };
    if exact { k.checked_mul(one_s) } else { None }
}

/// `log_base(v) = ln(v) / ln(base)`, both carried in the `Fixed` wide.
/// Used by `policy::log::dispatch` when the D57 borrow path is not
/// available (no `d57` / `wide` feature).
#[inline]
#[must_use]
pub(crate) fn log_with(
    raw: Int<2>,
    base_raw: Int<2>,
    scale: u32,
    working_digits: u32,
    mode: RoundingMode,
) -> Option<Int<2>> {
    log_with_raw(
        raw.as_i128(),
        base_raw.as_i128(),
        scale,
        working_digits,
        mode,
    )
    .map(Int::<2>::from_i128)
}

/// `i128` core of [`log_with`].
#[inline]
fn log_with_raw(
    raw: i128,
    base_raw: i128,
    scale: u32,
    working_digits: u32,
    mode: RoundingMode,
) -> Option<i128> {
    assert!(raw > 0, "D38::log: argument must be positive");
    assert!(base_raw > 0, "D38::log: base must be positive");
    let w = scale + working_digits;
    let pow = 10u128.pow(working_digits);
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(pow);
    let b_w = Fixed::from_u128_mag(base_raw as u128, false).mul_u128(pow);
    let ln_b = ln_fixed(b_w, w);
    assert!(
        !ln_b.is_zero(),
        "D38::log: base must not equal 1 (ln(1) is zero)"
    );
    let ratio = ln_fixed(v_w, w).div(ln_b, w);
    // Exact-power pin: `value == base^k` ⇒ result is exactly `k`.
    // Reduce the storage `base_raw` to its integer base (`base_raw /
    // 10^scale`) here, without forming `base · 10^scale`, so the pin's
    // integer-domain check never carries (and never overflows) the
    // scale factor — `0` flags a non-integer base (no exact pin).
    let k = ratio.round_to_nearest_int(w);
    let base_int = match 10i128.checked_pow(scale) {
        Some(one_s) if base_raw % one_s == 0 => base_raw / one_s,
        _ => 0,
    };
    if let Some(pinned) = log_exact_int_pin(raw, base_int, scale, k) {
        return Some(pinned);
    }
    ratio.round_to_i128_with(w, scale, mode)
}

/// Const-folded strict variant of [`log_with`] with the near-tie
/// protected terminal: the exact-integer pin, then the clear-of-tie
/// single shot, then (near a boundary) the exact rational-power pin —
/// `log_4(8) = 3/2` exactly on the scale-19 grid, `log_4(32) = 5/2`
/// exactly on the scale-0 half — and finally the Ziv walker for the
/// genuinely transcendental near-ties. `None` = result out of storage
/// range.
#[inline]
#[must_use]
pub(crate) fn log_strict<const SCALE: u32>(raw: Int<2>, base_raw: Int<2>, mode: RoundingMode) -> Option<Int<2>> {
    log_strict_raw(raw.as_i128(), base_raw.as_i128(), SCALE, mode).map(Int::<2>::from_i128)
}

/// `i128` core of [`log_strict`].
fn log_strict_raw(raw: i128, base_raw: i128, scale: u32, mode: RoundingMode) -> Option<i128> {
    assert!(raw > 0, "D38::log: argument must be positive");
    assert!(base_raw > 0, "D38::log: base must be positive");
    let w = scale + STRICT_GUARD;
    let pow = 10u128.pow(STRICT_GUARD);
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(pow);
    let b_w = Fixed::from_u128_mag(base_raw as u128, false).mul_u128(pow);
    let ln_b = ln_fixed(b_w, w);
    assert!(
        !ln_b.is_zero(),
        "D38::log: base must not equal 1 (ln(1) is zero)"
    );
    let ratio = ln_fixed(v_w, w).div(ln_b, w);
    // Exact-power pin (see `log_with_raw`).
    let k = ratio.round_to_nearest_int(w);
    let base_int = match 10i128.checked_pow(scale) {
        Some(one_s) if base_raw % one_s == 0 => base_raw / one_s,
        _ => 0,
    };
    if let Some(pinned) = log_exact_int_pin(raw, base_int, scale, k) {
        return Some(pinned);
    }
    match ratio.round_to_i128_clear_of_tie(w, scale, mode) {
        Some(r) => r,
        None => {
            // Near a boundary: try the exact rational-power pin before
            // walking (an exact rational log never resolves by escalation
            // — the residual at every depth is kernel noise around the
            // boundary).
            if let Some(num2) = ratio.double().round_to_i128_with(w, scale, RoundingMode::HalfToEven)
            {
                let one_s = 10u128.pow(scale);
                let (xn, xd) = reduce_fraction(raw as u128, one_s);
                let (bn, bd) = reduce_fraction(base_raw as u128, one_s);
                if let Some(pinned) =
                    log_rational_pow_pin(xn, xd, bn, bd, scale, num2, mode)
                {
                    return Some(pinned);
                }
            }
            narrow_ziv::walk_checked(
                ratio.round_to_i128_with(w, scale, mode),
                STRICT_GUARD,
                scale,
                mode,
                |g| log_ratio_ziv(raw, base_raw, scale, g),
            )
        }
    }
}

/// `log2(v) = ln(v) / ln(2)`, `Fixed`-wide fallback for D38.
#[inline]
#[must_use]
pub(crate) fn log2_with(raw: Int<2>, scale: u32, working_digits: u32, mode: RoundingMode) -> Option<Int<2>> {
    log2_with_raw(raw.as_i128(), scale, working_digits, mode).map(Int::<2>::from_i128)
}

/// `i128` core of [`log2_with`].
#[inline]
fn log2_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> Option<i128> {
    assert!(raw > 0, "D38::log2: argument must be positive");
    let w = scale + working_digits;
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(working_digits));
    let ratio = ln_fixed(v_w, w).div(wide_ln2(w), w);
    // Exact-power pin: `value == 2^k` ⇒ result is exactly `k`.
    let k = ratio.round_to_nearest_int(w);
    if let Some(pinned) = log_exact_int_pin(raw, 2, scale, k) {
        return Some(pinned);
    }
    ratio.round_to_i128_with(w, scale, mode)
}

/// `None` = result out of storage range. The strict terminal is
/// near-tie protected; `log2` of an on-grid rational is integer (the
/// exact pin) or IRRATIONAL (`2^(p/q)` is rational only for `q | p`), so
/// no rational-power pin is needed — the walker covers the rest.
#[inline]
#[must_use]
pub(crate) fn log2_strict<const SCALE: u32>(raw: Int<2>, mode: RoundingMode) -> Option<Int<2>> {
    log2_strict_raw(raw.as_i128(), SCALE, mode).map(Int::<2>::from_i128)
}

/// `i128` core of [`log2_strict`].
fn log2_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> Option<i128> {
    assert!(raw > 0, "D38::log2: argument must be positive");
    let w = scale + STRICT_GUARD;
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
    let ratio = ln_fixed(v_w, w).div(wide_ln2(w), w);
    let k = ratio.round_to_nearest_int(w);
    if let Some(pinned) = log_exact_int_pin(raw, 2, scale, k) {
        return Some(pinned);
    }
    match ratio.round_to_i128_clear_of_tie(w, scale, mode) {
        Some(r) => r,
        None => narrow_ziv::walk_checked(
            ratio.round_to_i128_with(w, scale, mode),
            STRICT_GUARD,
            scale,
            mode,
            |g| log_ratio_ziv(raw, -2, scale, g),
        ),
    }
}

/// `log10(v) = ln(v) / ln(10)`, `Fixed`-wide fallback for D38.
#[inline]
#[must_use]
pub(crate) fn log10_with(raw: Int<2>, scale: u32, working_digits: u32, mode: RoundingMode) -> Option<Int<2>> {
    log10_with_raw(raw.as_i128(), scale, working_digits, mode).map(Int::<2>::from_i128)
}

/// `i128` core of [`log10_with`].
#[inline]
fn log10_with_raw(raw: i128, scale: u32, working_digits: u32, mode: RoundingMode) -> Option<i128> {
    assert!(raw > 0, "D38::log10: argument must be positive");
    let w = scale + working_digits;
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(working_digits));
    let ratio = ln_fixed(v_w, w).div(wide_ln10(w), w);
    // Exact-power pin: `value == 10^k` ⇒ result is exactly `k`.
    let k = ratio.round_to_nearest_int(w);
    if let Some(pinned) = log_exact_int_pin(raw, 10, scale, k) {
        return Some(pinned);
    }
    ratio.round_to_i128_with(w, scale, mode)
}

/// `None` = result out of storage range. Near-tie protected like
/// [`log2_strict`] (`log10` of an on-grid rational is integer or
/// irrational).
#[inline]
#[must_use]
pub(crate) fn log10_strict<const SCALE: u32>(raw: Int<2>, mode: RoundingMode) -> Option<Int<2>> {
    log10_strict_raw(raw.as_i128(), SCALE, mode).map(Int::<2>::from_i128)
}

/// `i128` core of [`log10_strict`].
fn log10_strict_raw(raw: i128, scale: u32, mode: RoundingMode) -> Option<i128> {
    assert!(raw > 0, "D38::log10: argument must be positive");
    let w = scale + STRICT_GUARD;
    let v_w = Fixed::from_u128_mag(raw as u128, false).mul_u128(10u128.pow(STRICT_GUARD));
    let ratio = ln_fixed(v_w, w).div(wide_ln10(w), w);
    let k = ratio.round_to_nearest_int(w);
    if let Some(pinned) = log_exact_int_pin(raw, 10, scale, k) {
        return Some(pinned);
    }
    match ratio.round_to_i128_clear_of_tie(w, scale, mode) {
        Some(r) => r,
        None => narrow_ziv::walk_checked(
            ratio.round_to_i128_with(w, scale, mode),
            STRICT_GUARD,
            scale,
            mode,
            |g| log_ratio_ziv(raw, -10, scale, g),
        ),
    }
}

// ── Near-tie pins (see `trig_series_2limb::near_tie_pins`) ───────────
#[cfg(test)]
mod near_tie_pins {
    use super::*;

    #[test]
    fn ln_directed_near_one_deep_d38_s38() {
        // ln(1 + 1e-38) = δ − δ²/2 + … with δ = 1 ULP exactly: the true
        // value is δ·(1 − 5e-39), strictly below the grid line δ by a
        // deviation (depth 76.3) under every reachable fixed working
        // scale. Concavity decides: Floor/Trunc → 0, Ceiling → 1.
        // failing-first: the single shot at w = 68 saw a zero residual
        // and returned δ = 1 for Floor/Trunc. Oracle: the exact rational
        // partial + the strictly-negative tail (narrow_tie_derive.py).
        let raw = Int::<2>::from_i128(10_i128.pow(38) + 1);
        assert_eq!(
            ln_strict::<38>(raw, RoundingMode::Floor).map(|v| v.as_i128()),
            Some(0),
            "ln Floor"
        );
        assert_eq!(
            ln_strict::<38>(raw, RoundingMode::Trunc).map(|v| v.as_i128()),
            Some(0),
            "ln Trunc"
        );
        assert_eq!(
            ln_strict::<38>(raw, RoundingMode::Ceiling).map(|v| v.as_i128()),
            Some(1),
            "ln Ceiling"
        );
        // below 1: ln(1 − 1e-38) = −δ − δ²/2: strictly below −δ.
        let raw_lo = Int::<2>::from_i128(10_i128.pow(38) - 1);
        assert_eq!(
            ln_strict::<38>(raw_lo, RoundingMode::Floor).map(|v| v.as_i128()),
            Some(-2),
            "ln(1−ulp) Floor"
        );
        assert_eq!(
            ln_strict::<38>(raw_lo, RoundingMode::Trunc).map(|v| v.as_i128()),
            Some(-1),
            "ln(1−ulp) Trunc"
        );
    }

    #[test]
    fn ln_directed_deep_band_sweep() {
        // deep zone: deviation delta^2/(2*10^S) below 10^-(S+30) working
        // resolution <=> delta^2 < 2*10^(S-30)... in working units at w:
        // dev_working = delta^2/(2*10^S)*10^30 < 1 <=> delta^2 < 2*10^(S-30)
        let mut bad = 0u32;
        for s in 31u32..=38 {
            let one: i128 = 10_i128.pow(s);
            let max_d = ((2.0 * 10f64.powi(s as i32 - 30)).sqrt() as i128).max(1);
            let mut d: i128 = 1;
            while d <= max_d {
                let raw = Int::<2>::from_i128(one + d);
                let fl = ln_strict_dyn(s, raw, RoundingMode::Floor);
                let ce = ln_strict_dyn(s, raw, RoundingMode::Ceiling);
                if fl != Some(d - 1) || ce != Some(d) {
                    println!("LIVE ln(1+{d}ulp) S={s}: Floor={fl:?} (want {}), Ceil={ce:?} (want {d})", d-1);
                    bad += 1;
                }
                let raw_lo = Int::<2>::from_i128(one - d);
                let fl2 = ln_strict_dyn(s, raw_lo, RoundingMode::Floor);
                let tr2 = ln_strict_dyn(s, raw_lo, RoundingMode::Trunc);
                if fl2 != Some(-d - 1) || tr2 != Some(-d) {
                    println!("LIVE ln(1-{d}ulp) S={s}: Floor={fl2:?} (want {}), Trunc={tr2:?} (want {})", -d-1, -d);
                    bad += 1;
                }
                d = if d < 20 { d + 1 } else { d + d / 3 };
            }
        }
        assert_eq!(bad, 0, "ln deep directed band has live misrounds");
    }

    fn ln_strict_dyn(s: u32, raw: Int<2>, mode: RoundingMode) -> Option<i128> {
        // The STRICT path (const-scale): the fixed near-tie terminal.
        let v = match s {
            31 => ln_strict::<31>(raw, mode),
            32 => ln_strict::<32>(raw, mode),
            33 => ln_strict::<33>(raw, mode),
            34 => ln_strict::<34>(raw, mode),
            35 => ln_strict::<35>(raw, mode),
            36 => ln_strict::<36>(raw, mode),
            37 => ln_strict::<37>(raw, mode),
            38 => ln_strict::<38>(raw, mode),
            _ => unreachable!(),
        };
        v.map(|v| v.as_i128())
    }

    #[test]
    fn log_fractional_base_high_scale_budget_product_does_not_wrap() {
        // the rational-pin budget product `bl * e` can wrap u128
        // for an irreducible candidate denominator (q ~ 2*10^37) on
        // log(0.1096614350149675660535769418, 0.0182017066872921546105935121)
        // at D38<37> (the golden log.golden:18 panic). The saturating
        // budget must DEFER (walker), never wrap.
        let xr: i128 = 1096614350149675660535769418_i128 * 1_000_000_000;
        let br: i128 = 182017066872921546105935121_i128 * 1_000_000_000;
        for mode in [
            RoundingMode::HalfToEven,
            RoundingMode::Floor,
            RoundingMode::Ceiling,
        ] {
            let r = log_strict_raw(xr, br, 37, mode);
            assert!(r.is_some(), "log fractional-base s37 mode={mode:?}");
        }
    }

    #[test]
    fn log_rational_exact_d38() {
        // log_4(8) = 3/2 EXACTLY (8² = 4³): on the storage grid at
        // SCALE 19, so every mode must return exactly 1.5. failing-first:
        // the ln(8)/ln(4) single shot lands a hair off the grid line and
        // the directed modes stepped 1 LSB off the exact power.
        let one19 = 10_i128.pow(19);
        let x = Int::<2>::from_i128(8 * one19);
        let b = Int::<2>::from_i128(4 * one19);
        for mode in [
            RoundingMode::HalfToEven,
            RoundingMode::HalfAwayFromZero,
            RoundingMode::HalfTowardZero,
            RoundingMode::Floor,
            RoundingMode::Ceiling,
            RoundingMode::Trunc,
        ] {
            assert_eq!(
                log_strict_raw(x.as_i128(), b.as_i128(), 19, mode),
                Some(15 * one19 / 10),
                "log_4(8) mode={mode:?}"
            );
        }
    }

    #[test]
    fn log_rational_half_tie_s0() {
        // log_4(32) = 5/2 EXACTLY (32² = 4⁵): at SCALE 0 the true value
        // sits EXACTLY on the half between 2 and 3 — a genuine tie that
        // only exact integer arithmetic can certify (the working-scale
        // residual is kernel noise around the half). HalfToEven → 2
        // (even), HalfAwayFromZero → 3, HalfTowardZero → 2.
        let x = Int::<2>::from_i128(32);
        let b = Int::<2>::from_i128(4);
        assert_eq!(
            log_strict_raw(x.as_i128(), b.as_i128(), 0, RoundingMode::HalfToEven),
            Some(2),
            "log_4(32) HalfToEven"
        );
        assert_eq!(
            log_strict_raw(x.as_i128(), b.as_i128(), 0, RoundingMode::HalfAwayFromZero),
            Some(3),
            "log_4(32) HalfAwayFromZero"
        );
        assert_eq!(
            log_strict_raw(x.as_i128(), b.as_i128(), 0, RoundingMode::HalfTowardZero),
            Some(2),
            "log_4(32) HalfTowardZero"
        );
        // log_16(8) = 3/4 (8⁴ = 16³): a half-tie at SCALE 1 (7.5 tenths).
        let x8 = Int::<2>::from_i128(80);
        let b16 = Int::<2>::from_i128(160);
        assert_eq!(
            log_strict_raw(x8.as_i128(), b16.as_i128(), 1, RoundingMode::HalfToEven),
            Some(8),
            "log_16(8) s1 HalfToEven"
        );
        assert_eq!(
            log_strict_raw(x8.as_i128(), b16.as_i128(), 1, RoundingMode::HalfTowardZero),
            Some(7),
            "log_16(8) s1 HalfTowardZero"
        );
    }
}