• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eisenwave / std-big-int / 27377983558

11 Jun 2026 09:16PM UTC coverage: 96.536% (-0.2%) from 96.715%
27377983558

push

github

web-flow
Division From Burnikel-Ziegler (#247)

1122 of 1173 new or added lines in 10 files covered. (95.65%)

42 existing lines in 2 files now uncovered.

4961 of 5139 relevant lines covered (96.54%)

6840874.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.02
/src/divide.cpp
1
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2
// SPDX-License-Identifier: BSL-1.0
3

4
#include <beman/big_int/detail/div_impl.hpp>
5

6
#include <algorithm>
7
#include <bit>
8
#include <compare>
9
#include <cstddef>
10
#include <span>
11

12
#include <beman/big_int/detail/config.hpp>
13
#include <beman/big_int/detail/mul_impl.hpp>
14
#include <beman/big_int/detail/scratch_allocator.hpp>
15
#include <beman/big_int/detail/span_ops.hpp>
16
#include <beman/big_int/detail/wide_ops.hpp>
17

18
// The division tiers above the constexpr schoolbook kernels, compiled once:
19
// the Burnikel-Ziegler recursion, the divappr quotient-only chain, the
20
// exact Newton reciprocal, and the block Barrett driver. All work in the
21
// caller-sized type-erased scratch; internal products and FFT-tier
22
// workspaces come from its heap hooks, so a single compiled definition
23
// serves every allocator. Contracts live at the declarations in
24
// div_impl.hpp.
25

26
namespace beman::big_int::detail {
27

28
// ---------------------------------------------------------------------------
29
// Leaf of the Burnikel-Ziegler recursion: divide the 2n-limb window `a` by
30
// the n-limb divisor `b` (b.back() != 0) with the schoolbook kernel.
31
// Precondition: value(a) < beta^n * value(b), so the quotient fits n limbs.
32
// Postconditions: a.first(n) holds the remainder, a.subspan(n) is zero, and
33
// all n limbs of `q` are written.
34
// ---------------------------------------------------------------------------
35
inline void divide_dc_basecase(const std::span<uint_multiprecision_t>       a,
141,745✔
36
                               const std::span<const uint_multiprecision_t> b,
37
                               const std::span<uint_multiprecision_t>       q,
38
                               scratch_allocator_base&                      scratch) noexcept {
39
    const std::size_t n = b.size();
141,745✔
40
    BEMAN_BIG_INT_DEBUG_ASSERT(a.size() == 2 * n);
141,745✔
41
    BEMAN_BIG_INT_DEBUG_ASSERT(q.size() == n);
141,745✔
42
    BEMAN_BIG_INT_DEBUG_ASSERT(!b.empty());
141,745✔
43
    BEMAN_BIG_INT_DEBUG_ASSERT(b.back() != 0);
141,745✔
44

45
    const std::size_t a_size = trimmed_size_span(a);
141,745✔
46
    const auto        a_view = std::span<const uint_multiprecision_t>{a.data(), a_size};
141,745✔
47

48
    // value(a) < value(b): quotient 0, remainder already in place (a's limbs
49
    // above its trimmed size are zero by definition).
50
    if (compare_unsigned_spans(a_view, b) == std::strong_ordering::less) {
141,745✔
51
        std::ranges::fill(q, uint_multiprecision_t{0});
37,311✔
52
        return;
93,370✔
53
    }
54

55
    // Single-limb divisor, reachable only under small threshold overrides.
56
    if (n == 1) {
104,434✔
57
        const std::span<uint_multiprecision_t> q_tmp = scratch.allocate(a_size);
56,059✔
58
        const uint_multiprecision_t            rem   = divide_unsigned_short(q_tmp, a_view, b[0]);
56,059✔
59
        BEMAN_BIG_INT_DEBUG_ASSERT(a_size < 2 || q_tmp[1] == 0);
56,059✔
60
        q[0] = q_tmp[0];
56,059✔
61
        a[0] = rem;
56,059✔
62
        a[1] = 0;
56,059✔
63
        scratch.deallocate(a_size);
56,059✔
64
        return;
56,059✔
65
    }
66

67
    // value(a) >= value(b) and b.back() != 0 force a_size >= n, satisfying
68
    // the divide_unsigned preconditions.
69
    BEMAN_BIG_INT_DEBUG_ASSERT(a_size >= n);
48,375✔
70
    const std::size_t                      q_size = a_size - n + 1;
48,375✔
71
    const std::span<uint_multiprecision_t> q_tmp  = scratch.allocate(q_size);
48,375✔
72
    const std::span<uint_multiprecision_t> r_tmp  = scratch.allocate(a_size + 1);
48,375✔
73

74
    divide_unsigned(q_tmp, r_tmp, a_view, b, scratch);
48,375✔
75

76
    // The 2n/n precondition keeps quotient and remainder below beta^n; copy
77
    // them back into the window layout.
78
    BEMAN_BIG_INT_DEBUG_ASSERT(q_size <= n + 1);
48,375✔
79
    BEMAN_BIG_INT_DEBUG_ASSERT(q_size <= n || q_tmp[n] == 0);
48,375✔
80
    const std::size_t q_copy = std::min(q_size, n);
48,375✔
81
    std::ranges::copy(q_tmp.first(q_copy), q.begin());
48,375✔
82
    std::ranges::fill(q.subspan(q_copy), uint_multiprecision_t{0});
48,375✔
83

84
    BEMAN_BIG_INT_DEBUG_ASSERT(is_span_zero(std::span<const uint_multiprecision_t>{r_tmp.data() + n, a_size + 1 - n}));
48,375✔
85
    std::ranges::copy(r_tmp.first(n), a.begin());
48,375✔
86
    std::ranges::fill(a.subspan(n), uint_multiprecision_t{0});
48,375✔
87

88
    scratch.deallocate(a_size + 1);
48,375✔
89
    scratch.deallocate(q_size);
48,375✔
90
}
91

92
void divide_dc_3n2n(std::span<uint_multiprecision_t>       v,
93
                    std::span<const uint_multiprecision_t> b,
94
                    std::span<uint_multiprecision_t>       q,
95
                    scratch_allocator_base&                scratch,
96
                    std::size_t                            threshold);
97

98
// ---------------------------------------------------------------------------
99
// Burnikel-Ziegler D_2n/n: divide the 2n-limb window `a` by the n-limb
100
// normalized divisor `b` (top bit set) via two D_3n/2n calls on half blocks.
101
// Precondition: value(a) < beta^n * value(b), so the quotient fits n limbs.
102
// Postconditions: a.first(n) holds the remainder, a.subspan(n) is zero, and
103
// all n limbs of `q` are written.
104
// `threshold` propagates through the recursion (unlike the multiplication
105
// kernels' top-only cutoff_override) so tests can force deep recursion on
106
// small operands; production callers pass burnikel_ziegler_cutoff.
107
// ---------------------------------------------------------------------------
108
void divide_dc_2n1n(const std::span<uint_multiprecision_t>       a,
250,804✔
109
                    const std::span<const uint_multiprecision_t> b,
110
                    const std::span<uint_multiprecision_t>       q,
111
                    scratch_allocator_base&                      scratch,
112
                    const std::size_t                            threshold) {
113
    const std::size_t n = b.size();
250,804✔
114
    BEMAN_BIG_INT_DEBUG_ASSERT(a.size() == 2 * n);
250,804✔
115
    BEMAN_BIG_INT_DEBUG_ASSERT(q.size() == n);
250,804✔
116
    // value(a) < beta^n * value(b) is exactly "high half of a < b".
117
    BEMAN_BIG_INT_DEBUG_ASSERT(compare_unsigned_spans(a.subspan(n), b) == std::strong_ordering::less);
250,804✔
118

119
    if ((n % 2) != 0 || n < threshold) {
250,804✔
120
        divide_dc_basecase(a, b, q, scratch);
140,005✔
121
        return;
140,005✔
122
    }
123

124
    const std::size_t h = n / 2;
110,799✔
125

126
    // High quotient half from the top three quarter-blocks; the remainder
127
    // lands in a[h..3h) and a[3h..4h) is zeroed.
128
    divide_dc_3n2n(a.subspan(h, 3 * h), b, q.subspan(h, h), scratch, threshold);
110,799✔
129

130
    // Low quotient half: [low quarter-block | remainder] is contiguous.
131
    divide_dc_3n2n(a.first(3 * h), b, q.first(h), scratch, threshold);
110,799✔
132
}
133

134
// ---------------------------------------------------------------------------
135
// Burnikel-Ziegler D_3n/2n: divide the 3h-limb window `v` by the 2h-limb
136
// normalized divisor `b` (top bit set): estimate the quotient from the high
137
// 2h limbs of `v` against b's high half, fix up with one h x h product and
138
// at most two add-back corrections.
139
// Precondition: value(v) < beta^h * value(b), so the quotient fits h limbs.
140
// Postconditions: v.first(2h) holds the remainder, v.subspan(2h) is zero,
141
// and all h limbs of `q` are written.
142
// ---------------------------------------------------------------------------
143
void divide_dc_3n2n(const std::span<uint_multiprecision_t>       v,
229,212✔
144
                    const std::span<const uint_multiprecision_t> b,
145
                    const std::span<uint_multiprecision_t>       q,
146
                    scratch_allocator_base&                      scratch,
147
                    const std::size_t                            threshold) {
148
    constexpr uint_multiprecision_t max_limb = ~uint_multiprecision_t{0};
229,212✔
149

150
    const std::size_t h = q.size();
229,212✔
151
    BEMAN_BIG_INT_DEBUG_ASSERT(v.size() == 3 * h);
229,212✔
152
    BEMAN_BIG_INT_DEBUG_ASSERT(b.size() == 2 * h);
229,212✔
153
    BEMAN_BIG_INT_DEBUG_ASSERT((b.back() >> (width_v<uint_multiprecision_t> - 1)) == 1);
229,212✔
154

155
    const auto b1 = b.subspan(h, h); // high half of the divisor
229,212✔
156
    const auto b2 = b.first(h);      // low half of the divisor
229,212✔
157

158
    const bool top_below_b1 = compare_unsigned_spans(v.subspan(2 * h, h), b1) == std::strong_ordering::less;
229,212✔
159
    bool       r1_carry     = false;
229,212✔
160
    if (top_below_b1) {
229,212✔
161
        // Recursive estimate q = floor(v_high2h / b1); R1 is left in v[h..2h)
162
        // and v[2h..3h) is zeroed.
163
        divide_dc_2n1n(v.subspan(h, 2 * h), b1, q, scratch, threshold);
229,161✔
164
    } else {
165
        // The precondition forces v's top h limbs to equal b1 exactly and the
166
        // true quotient to within 2 of beta^h - 1:
167
        //   q = beta^h - 1,  R1 = v_mid + b1  (fits h limbs plus a carry bit).
168
        BEMAN_BIG_INT_DEBUG_ASSERT(compare_unsigned_spans(v.subspan(2 * h, h), b1) == std::strong_ordering::equal);
51✔
169
        std::ranges::fill(q, max_limb);
51✔
170
        r1_carry = add_unsigned_spans(v.subspan(h, h), v.subspan(h, h), b1);
51✔
171
        std::ranges::fill(v.subspan(2 * h), uint_multiprecision_t{0});
51✔
172
    }
173

174
    // d := q * b2 (2h limbs). The all-ones branch needs no multiply:
175
    // (beta^h - 1) * b2 == (b2 << h limbs) - b2.
176
    const std::span<uint_multiprecision_t> d = scratch.allocate(2 * h);
229,212✔
177
    std::ranges::fill(d, uint_multiprecision_t{0});
229,212✔
178
    if (top_below_b1) {
229,212✔
179
        multiply_runtime_any(d, q, b2, scratch.heap());
229,161✔
180
    } else {
181
        std::ranges::copy(b2, d.begin() + static_cast<std::ptrdiff_t>(h));
102✔
182
        subtract_unsigned_spans(d, d, b2);
51✔
183
    }
184

185
    // Combine: r_hat = R1 * beta^h + v_low - d over the 2h-limb window; the
186
    // carry/borrow pair tracks the limb at position 2h. net is in {-1, 0}:
187
    // d < beta^2h bounds r_hat > -beta^2h, and r_hat < value(b) < beta^2h.
188
    const bool borrow = subtract_unsigned_spans_borrow_out(v.first(2 * h), v.first(2 * h), d);
229,212✔
189
    int        net    = static_cast<int>(r1_carry) - static_cast<int>(borrow);
229,212✔
190
    BEMAN_BIG_INT_DEBUG_ASSERT(net <= 0);
229,212✔
191

192
    // While r_hat < 0: q -= 1, r_hat += b. Normalization (2 * value(b) >=
193
    // beta^2h) bounds the loop at two iterations.
194
    [[maybe_unused]] int corrections = 0;
229,212✔
195
    while (net < 0) {
281,062✔
196
        const bool q_borrow = decrement_span(q);
51,850✔
197
        BEMAN_BIG_INT_DEBUG_ASSERT(!q_borrow);
51,850✔
198
        net += static_cast<int>(add_unsigned_spans(v.first(2 * h), v.first(2 * h), b));
51,850✔
199
        ++corrections;
51,850✔
200
    }
201
    BEMAN_BIG_INT_DEBUG_ASSERT(corrections <= 2);
229,212✔
202
    BEMAN_BIG_INT_DEBUG_ASSERT(compare_unsigned_spans(v.first(2 * h), b) == std::strong_ordering::less);
229,212✔
203

204
    scratch.deallocate(2 * h);
229,212✔
205
}
229,212✔
206

207
// ---------------------------------------------------------------------------
208
// Approximate D_2n/n (the GMP dcpi1_divappr_q_n idea in Burnikel-Ziegler
209
// dress): the high quotient half comes from the exact D_3n/2n on the top
210
// three quarter-blocks -- its remainder is needed to continue -- but the low
211
// half recurses approximately on that remainder's top 2h limbs against the
212
// divisor's high half only. The window's low quarter and the divisor's low
213
// half are never read below the top level, so the whole low subtree skips
214
// its remainder multiplies; that is the divappr saving.
215
//
216
// Same window contract as divide_dc_2n1n (a is 2n limbs with its high half
217
// strictly below b; all n quotient limbs written; `a` is consumed as
218
// workspace -- unlike the exact routine it does NOT leave a remainder).
219
// The computed quotient satisfies
220
//   q <= q' <= q + divappr_quotient_slack(n, threshold):
221
// each halving level divides by a divisor whose low half was dropped,
222
// contributing at most one quotient ulp on top of the recursion below it
223
// (GMP's flat +1 bound for this construction relies on threading an extra
224
// fraction limb through the recursion, which this shape does not carry).
225
// The differential suite measures the accumulation: observed maxima track
226
// the level count minus one, so the +2 here is honest headroom.
227
// ---------------------------------------------------------------------------
228
void divide_dc_divappr(const std::span<uint_multiprecision_t>       a,
11,447✔
229
                       const std::span<const uint_multiprecision_t> b,
230
                       const std::span<uint_multiprecision_t>       q,
231
                       scratch_allocator_base&                      scratch,
232
                       const std::size_t                            threshold) {
233
    constexpr uint_multiprecision_t max_limb = ~uint_multiprecision_t{0};
11,447✔
234

235
    const std::size_t n = b.size();
11,447✔
236
    BEMAN_BIG_INT_DEBUG_ASSERT(a.size() == 2 * n);
11,447✔
237
    BEMAN_BIG_INT_DEBUG_ASSERT(q.size() == n);
11,447✔
238
    BEMAN_BIG_INT_DEBUG_ASSERT((b.back() >> (width_v<uint_multiprecision_t> - 1)) == 1);
11,447✔
239
    BEMAN_BIG_INT_DEBUG_ASSERT(compare_unsigned_spans(a.subspan(n), b) == std::strong_ordering::less);
11,447✔
240

241
    if ((n % 2) != 0 || n < threshold) {
11,447✔
242
        // Degenerate leaves (deep threshold overrides only): exact division
243
        // satisfies the approximate contract trivially.
244
        if (n < 3) {
3,833✔
245
            divide_dc_basecase(a, b, q, scratch);
1,740✔
246
            return;
1,740✔
247
        }
248

249
        const std::size_t a_size = trimmed_size_span(a);
2,093✔
250
        const auto        a_view = std::span<const uint_multiprecision_t>{a.data(), a_size};
2,093✔
251

252
        // Window value below (or one limb of) the divisor: the quotient is
253
        // 0 or 1 by comparison; no division needed.
254
        if (a_size <= n) {
2,093✔
NEW
255
            std::ranges::fill(q, uint_multiprecision_t{0});
×
NEW
256
            if (compare_unsigned_spans(a_view, b) != std::strong_ordering::less) {
×
NEW
257
                q[0] = 1;
×
258
            }
NEW
259
            return;
×
260
        }
261

262
        // The approximate basecase produces a_size - n + 1 <= n + 1 digits;
263
        // a top overflow digit means the true quotient is within the slack
264
        // of beta^n - 1, so the window saturates.
265
        const std::size_t                      q_len = a_size - n + 1;
2,093✔
266
        const std::span<uint_multiprecision_t> q_tmp = scratch.allocate(q_len);
2,093✔
267
        divide_unsigned_approx(q_tmp, a_view, b, scratch);
2,093✔
268
        BEMAN_BIG_INT_DEBUG_ASSERT(q_len <= n || q_tmp[n] <= 1);
2,093✔
269
        if (q_len > n && q_tmp[n] != 0) {
2,093✔
NEW
270
            std::ranges::fill(q, max_limb);
×
271
        } else {
272
            const std::size_t q_copy = std::min(q_len, n);
2,093✔
273
            std::ranges::copy(q_tmp.first(q_copy), q.begin());
2,093✔
274
            std::ranges::fill(q.subspan(q_copy), uint_multiprecision_t{0});
2,093✔
275
        }
276
        scratch.deallocate(q_len);
2,093✔
277
        return;
2,093✔
278
    }
279

280
    const std::size_t h = n / 2;
7,614✔
281

282
    // High quotient half, exactly as divide_dc_2n1n: remainder (< b) lands
283
    // in a[h..3h) and a[3h..4h) is zeroed.
284
    divide_dc_3n2n(a.subspan(h, 3 * h), b, q.subspan(h, h), scratch, threshold);
7,614✔
285

286
    // Low quotient half approximately: the remainder's top 2h limbs against
287
    // the divisor's high half. remainder < b only bounds the inner window's
288
    // high half by b1 inclusively; equality saturates (the true low half is
289
    // then within the slack of beta^h - 1).
290
    const auto b1 = b.subspan(h, h);
7,614✔
291
    if (compare_unsigned_spans(a.subspan(2 * h, h), b1) == std::strong_ordering::less) {
7,614✔
292
        divide_dc_divappr(a.subspan(h, 2 * h), b1, q.first(h), scratch, threshold);
7,601✔
293
    } else {
294
        BEMAN_BIG_INT_DEBUG_ASSERT(compare_unsigned_spans(a.subspan(2 * h, h), b1) == std::strong_ordering::equal);
13✔
295
        std::ranges::fill(q.first(h), max_limb);
13✔
296
    }
297
}
298

299
// ---------------------------------------------------------------------------
300
// Burnikel-Ziegler division driver (the paper's Algorithm 3): pad the divisor
301
// length to n = j * 2^k so halving bottoms out at j-limb leaves, normalize
302
// both operands by sigma = n * limb_bits - bitlen(divisor), then march
303
// fixed-size dividend blocks through divide_dc_2n1n from the top down, each
304
// window picking up the previous remainder as its high half.
305
// Same outer contract as divide_unsigned: trimmed inputs, divisor.size() >= 2,
306
// dividend.size() >= divisor.size(), quotient.size() >= dividend.size() -
307
// divisor.size() + 1, remainder.size() >= dividend.size() + 1, no aliasing;
308
// both output spans are fully written (zero above the significant limbs).
309
// This overload works in caller-provided scratch, which must hold at least
310
// burnikel_ziegler_storage_size(plan...) limbs for `plan` as computed by
311
// burnikel_ziegler_plan on the same operands.
312
// ---------------------------------------------------------------------------
313
void divide_burnikel_ziegler(const std::span<uint_multiprecision_t>       quotient,
5,128✔
314
                             const std::span<uint_multiprecision_t>       remainder,
315
                             const std::span<const uint_multiprecision_t> dividend,
316
                             const std::span<const uint_multiprecision_t> divisor,
317
                             scratch_allocator_base&                      scratch,
318
                             const burnikel_ziegler_params                plan) {
319
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.size() >= 2);
5,128✔
320
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.back() != 0);
5,128✔
321
    BEMAN_BIG_INT_DEBUG_ASSERT(!dividend.empty());
5,128✔
322
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.back() != 0);
5,128✔
323
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.size() >= divisor.size());
5,128✔
324
    BEMAN_BIG_INT_DEBUG_ASSERT(quotient.size() >= dividend.size() - divisor.size() + 1);
5,128✔
325
    BEMAN_BIG_INT_DEBUG_ASSERT(remainder.size() >= dividend.size() + 1);
5,128✔
326

327
    constexpr std::size_t limb_bits = width_v<uint_multiprecision_t>;
5,128✔
328

329
    const std::size_t s   = divisor.size();
5,128✔
330
    const std::size_t m   = dividend.size();
5,128✔
331
    const std::size_t thr = plan.threshold;
5,128✔
332
    const std::size_t n   = plan.block_limbs;
5,128✔
333
    const std::size_t t   = plan.blocks;
5,128✔
334

335
    // Normalization shift: whole limbs from the padding plus the bits that
336
    // bring the divisor's top bit to the top. limb_off == n - s exactly
337
    // because the trimmed divisor has (s-1)*limb_bits < bitlen <= s*limb_bits.
338
    const std::size_t limb_off = n - s;
5,128✔
339
    const unsigned bit_off = static_cast<unsigned>(limb_bits) - static_cast<unsigned>(std::bit_width(divisor.back()));
5,128✔
340

341
    // b_hat = divisor << sigma: n limbs, top bit set.
342
    const std::span<uint_multiprecision_t> b_hat = scratch.allocate(n);
5,128✔
343
    std::ranges::fill(b_hat, uint_multiprecision_t{0});
5,128✔
344
    std::ranges::copy(divisor, b_hat.begin() + static_cast<std::ptrdiff_t>(limb_off));
10,256✔
345
    if (bit_off != 0) {
5,128✔
346
        const std::size_t b_hat_size = shift_left_n(b_hat, n, bit_off);
2,327✔
347
        BEMAN_BIG_INT_DEBUG_ASSERT(b_hat_size == n);
2,327✔
348
    }
349
    BEMAN_BIG_INT_DEBUG_ASSERT((b_hat.back() >> (limb_bits - 1)) == 1);
5,128✔
350

351
    // w = dividend << sigma, zero-extended to t blocks of n limbs.
352
    const std::span<uint_multiprecision_t> w = scratch.allocate(t * n);
5,128✔
353
    std::ranges::fill(w, uint_multiprecision_t{0});
5,128✔
354
    std::ranges::copy(dividend, w.begin() + static_cast<std::ptrdiff_t>(limb_off));
10,256✔
355
    if (bit_off != 0) {
5,128✔
356
        const std::size_t w_size = shift_left_n(w, limb_off + m, bit_off);
2,327✔
357
        BEMAN_BIG_INT_DEBUG_ASSERT(w_size <= t * n);
2,327✔
358
    }
359

360
    // March the windows from the top down; window i leaves its remainder in
361
    // w[i*n..(i+1)*n), the high half of window i-1.
362
    const std::span<uint_multiprecision_t> q_work = scratch.allocate((t - 1) * n);
5,128✔
363
    for (std::size_t i = t - 1; i-- > 0;) {
19,649✔
364
        divide_dc_2n1n(w.subspan(i * n, 2 * n), b_hat, q_work.subspan(i * n, n), scratch, thr);
14,521✔
365
    }
366

367
    // Quotient blocks concatenate exactly (each is below beta^n); trim and
368
    // copy out.
369
    const std::size_t q_size = trimmed_size_span(q_work);
5,128✔
370
    BEMAN_BIG_INT_DEBUG_ASSERT(q_size <= quotient.size());
5,128✔
371
    std::ranges::copy(q_work.first(q_size), quotient.begin());
5,128✔
372
    std::ranges::fill(quotient.subspan(q_size), uint_multiprecision_t{0});
5,128✔
373

374
    // Remainder: undo the normalization shift on the final window's low half.
375
    if (bit_off != 0) {
5,128✔
376
        const uint_multiprecision_t dropped = shift_right_n(w.first(n), bit_off);
2,327✔
377
        BEMAN_BIG_INT_DEBUG_ASSERT(dropped == 0);
2,327✔
378
    }
379
    BEMAN_BIG_INT_DEBUG_ASSERT(limb_off == 0 ||
5,128✔
380
                               is_span_zero(std::span<const uint_multiprecision_t>{w.data(), limb_off}));
381
    std::ranges::copy(w.subspan(limb_off, n - limb_off), remainder.begin());
5,128✔
382
    std::ranges::fill(remainder.subspan(n - limb_off), uint_multiprecision_t{0});
5,128✔
383
}
5,128✔
384

385
// ---------------------------------------------------------------------------
386
// Approximate quotient driver: identical plan, normalization, and window
387
// march to divide_burnikel_ziegler, but the lowest window -- the only one
388
// whose remainder nobody needs -- runs divide_dc_divappr, and no remainder
389
// is materialized. The blocks above it stay exact (each remainder feeds the
390
// next window), so the whole quotient's slack is the last block's:
391
//   q <= q' <= q + divappr_quotient_slack(plan.block_limbs, plan.threshold).
392
// The normalization scaling multiplies both operands by 2^sigma and leaves
393
// the quotient invariant. Scratch: burnikel_ziegler_storage_size on the
394
// same plan (the approximate leaf path allocates strictly less than the
395
// exact one).
396
// ---------------------------------------------------------------------------
397
void divide_quotient_appr(const std::span<uint_multiprecision_t>       quotient,
2,932✔
398
                          const std::span<const uint_multiprecision_t> dividend,
399
                          const std::span<const uint_multiprecision_t> divisor,
400
                          scratch_allocator_base&                      scratch,
401
                          const burnikel_ziegler_params                plan) {
402
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.size() >= 2);
2,932✔
403
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.back() != 0);
2,932✔
404
    BEMAN_BIG_INT_DEBUG_ASSERT(!dividend.empty());
2,932✔
405
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.back() != 0);
2,932✔
406
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.size() >= divisor.size());
2,932✔
407
    BEMAN_BIG_INT_DEBUG_ASSERT(quotient.size() >= dividend.size() - divisor.size() + 1);
2,932✔
408

409
    constexpr std::size_t limb_bits = width_v<uint_multiprecision_t>;
2,932✔
410

411
    const std::size_t s   = divisor.size();
2,932✔
412
    const std::size_t m   = dividend.size();
2,932✔
413
    const std::size_t thr = plan.threshold;
2,932✔
414
    const std::size_t n   = plan.block_limbs;
2,932✔
415
    const std::size_t t   = plan.blocks;
2,932✔
416

417
    const std::size_t limb_off = n - s;
2,932✔
418
    const unsigned bit_off = static_cast<unsigned>(limb_bits) - static_cast<unsigned>(std::bit_width(divisor.back()));
2,932✔
419

420
    // b_hat = divisor << sigma: n limbs, top bit set.
421
    const std::span<uint_multiprecision_t> b_hat = scratch.allocate(n);
2,932✔
422
    std::ranges::fill(b_hat, uint_multiprecision_t{0});
2,932✔
423
    std::ranges::copy(divisor, b_hat.begin() + static_cast<std::ptrdiff_t>(limb_off));
5,864✔
424
    if (bit_off != 0) {
2,932✔
425
        const std::size_t b_hat_size = shift_left_n(b_hat, n, bit_off);
1,441✔
426
        BEMAN_BIG_INT_DEBUG_ASSERT(b_hat_size == n);
1,441✔
427
    }
428
    BEMAN_BIG_INT_DEBUG_ASSERT((b_hat.back() >> (limb_bits - 1)) == 1);
2,932✔
429

430
    // w = dividend << sigma, zero-extended to t blocks of n limbs.
431
    const std::span<uint_multiprecision_t> w = scratch.allocate(t * n);
2,932✔
432
    std::ranges::fill(w, uint_multiprecision_t{0});
2,932✔
433
    std::ranges::copy(dividend, w.begin() + static_cast<std::ptrdiff_t>(limb_off));
5,864✔
434
    if (bit_off != 0) {
2,932✔
435
        const std::size_t w_size = shift_left_n(w, limb_off + m, bit_off);
1,441✔
436
        BEMAN_BIG_INT_DEBUG_ASSERT(w_size <= t * n);
1,441✔
437
    }
438

439
    // Exact windows from the top down, then the approximate lowest window.
440
    const std::span<uint_multiprecision_t> q_work = scratch.allocate((t - 1) * n);
2,932✔
441
    for (std::size_t i = t - 1; i-- > 1;) {
9,140✔
442
        divide_dc_2n1n(w.subspan(i * n, 2 * n), b_hat, q_work.subspan(i * n, n), scratch, thr);
6,208✔
443
    }
444
    divide_dc_divappr(w.first(2 * n), b_hat, q_work.first(n), scratch, thr);
2,932✔
445

446
    const std::size_t q_size = trimmed_size_span(q_work);
2,932✔
447
    BEMAN_BIG_INT_DEBUG_ASSERT(q_size <= quotient.size());
2,932✔
448
    std::ranges::copy(q_work.first(q_size), quotient.begin());
2,932✔
449
    std::ranges::fill(quotient.subspan(q_size), uint_multiprecision_t{0});
2,932✔
450

451
    scratch.deallocate((t - 1) * n);
2,932✔
452
    scratch.deallocate(t * n);
2,932✔
453
    scratch.deallocate(n);
2,932✔
454
}
2,932✔
455

456
// ---------------------------------------------------------------------------
457
// Exact quotient-only division: contract and the fraction-limb argument in
458
// div_impl.hpp at the declaration. Works entirely in `scratch` (sized by
459
// divide_quotient_storage_size), including the padded numerator.
460
// ---------------------------------------------------------------------------
461
void divide_quotient(const std::span<uint_multiprecision_t>       quotient,
2,932✔
462
                     const std::span<const uint_multiprecision_t> dividend,
463
                     const std::span<const uint_multiprecision_t> divisor,
464
                     scratch_allocator_base&                      scratch,
465
                     const std::size_t                            threshold_override) {
466
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.size() >= 2);
2,932✔
467
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.back() != 0);
2,932✔
468
    BEMAN_BIG_INT_DEBUG_ASSERT(!dividend.empty());
2,932✔
469
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.back() != 0);
2,932✔
470
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.size() >= divisor.size());
2,932✔
471
    BEMAN_BIG_INT_DEBUG_ASSERT(quotient.size() >= dividend.size() - divisor.size() + 1);
2,932✔
472

473
    const std::size_t m   = dividend.size();
2,932✔
474
    const std::size_t s   = divisor.size();
2,932✔
475
    const std::size_t qn1 = m - s + 1;
2,932✔
476

477
    // Padded numerator dividend * B.
478
    const std::span<uint_multiprecision_t> padded = scratch.allocate(m + 1);
2,932✔
479
    padded[0]                                     = 0;
2,932✔
480
    std::ranges::copy(dividend, padded.begin() + 1);
5,864✔
481
    const auto padded_view = std::span<const uint_multiprecision_t>{padded.data(), m + 1};
2,932✔
482

483
    const burnikel_ziegler_params plan  = burnikel_ziegler_plan(padded_view, divisor, threshold_override);
2,932✔
484
    const std::size_t             slack = divappr_quotient_slack(plan.block_limbs, plan.threshold);
2,932✔
485

486
    const std::span<uint_multiprecision_t> q_ext = scratch.allocate(qn1 + 2);
2,932✔
487
    divide_quotient_appr(q_ext, padded_view, divisor, scratch, plan);
2,932✔
488

489
    // The extended quotient can spill one digit past q * B only when the
490
    // true quotient is exactly beta^qn1 - 1 and the fraction sits within
491
    // the slack of B: the answer is all-ones, no verification needed.
492
    if (q_ext[qn1 + 1] != 0) {
2,932✔
NEW
493
        std::ranges::fill(quotient.first(qn1), ~uint_multiprecision_t{0});
×
NEW
494
        std::ranges::fill(quotient.subspan(qn1), uint_multiprecision_t{0});
×
NEW
495
        scratch.deallocate(qn1 + 2);
×
NEW
496
        scratch.deallocate(m + 1);
×
497
        return;
2,558✔
498
    }
499

500
    const auto integer_part = std::span<const uint_multiprecision_t>{q_ext.data() + 1, qn1};
2,932✔
501
    if (q_ext[0] >= slack) {
2,932✔
502
        std::ranges::copy(integer_part, quotient.begin());
2,558✔
503
        std::ranges::fill(quotient.subspan(qn1), uint_multiprecision_t{0});
2,558✔
504
        scratch.deallocate(qn1 + 2);
2,558✔
505
        scratch.deallocate(m + 1);
2,558✔
506
        return;
2,558✔
507
    }
508

509
    // Ambiguous fraction: the integer part is q or q + 1. One product
510
    // decides; the subtraction never borrows because q >= 1 here
511
    // (dividend >= divisor).
512
    const std::size_t                      i_size = trimmed_size_span(integer_part);
374✔
513
    const std::span<uint_multiprecision_t> prod   = scratch.allocate(m + 2);
374✔
514
    std::ranges::fill(prod, uint_multiprecision_t{0});
374✔
515
    if (i_size != 0) {
374✔
516
        multiply_runtime_any(prod, integer_part.first(i_size), divisor, scratch.heap());
374✔
517
    }
518
    std::ranges::copy(integer_part, quotient.begin());
374✔
519
    std::ranges::fill(quotient.subspan(qn1), uint_multiprecision_t{0});
374✔
520
    if (compare_unsigned_spans(std::span<const uint_multiprecision_t>{prod.data(), m + 2}, dividend) ==
374✔
521
        std::strong_ordering::greater) {
522
        const bool borrow = decrement_span(quotient.first(qn1));
129✔
523
        BEMAN_BIG_INT_DEBUG_ASSERT(!borrow);
129✔
524
    }
525
    scratch.deallocate(m + 2);
374✔
526
    scratch.deallocate(qn1 + 2);
374✔
527
    scratch.deallocate(m + 1);
374✔
528
}
529
// ---------------------------------------------------------------------------
530
// Exact scaled reciprocal of a normalized divisor (the span counterpart of
531
// reciprocal_word): writes the n limbs of I = floor((B^{2n} - 1) / d) - B^n
532
// into `inverse`.
533
// Newton iteration in the shape of MCA algorithm 3.5: the top-half
534
// reciprocal seeds the candidate
535
//   X ~= X_h * B^{n-h} + X_h * (B^{n+h} - d * X_h) / B^{2h},
536
// which lands within a few ulps of the target; the exact residual
537
// B^{2n} - 1 - d * X then pins it down. The fix loop is the correctness
538
// argument, so no delicate per-level error analysis is load-bearing.
539
// Preconditions: inverse.size() == d.size() >= 2, d.back()'s top bit set, no
540
// aliasing; `scratch` provides reciprocal_span_storage_size(...) limbs.
541
// `threshold_override` is a test-only escape hatch forcing deep recursion.
542
// ---------------------------------------------------------------------------
543
void reciprocal_span(const std::span<uint_multiprecision_t>       inverse,
3,407✔
544
                     const std::span<const uint_multiprecision_t> d,
545
                     scratch_allocator_base&                      scratch,
546
                     const std::size_t                            threshold_override) {
547
    constexpr uint_multiprecision_t max_limb = ~uint_multiprecision_t{0};
3,407✔
548

549
    const std::size_t n = d.size();
3,407✔
550
    BEMAN_BIG_INT_DEBUG_ASSERT(inverse.size() == n);
3,407✔
551
    BEMAN_BIG_INT_DEBUG_ASSERT(n >= 2);
3,407✔
552
    BEMAN_BIG_INT_DEBUG_ASSERT((d.back() >> (width_v<uint_multiprecision_t> - 1)) == 1);
3,407✔
553
    BEMAN_BIG_INT_DEBUG_ASSERT(inverse.data() != d.data());
3,407✔
554

555
    const std::size_t thr = threshold_override != 0 ? threshold_override : reciprocal_span_cutoff;
3,407✔
556
    BEMAN_BIG_INT_DEBUG_ASSERT(thr >= 2);
3,407✔
557

558
    if (n <= thr) {
3,407✔
559
        // I is the low half of floor((B^{2n} - 1) / d); the quotient's top
560
        // limb is exactly 1 because normalization keeps X in [B^n, 2B^n).
561
        const std::span<uint_multiprecision_t> ones = scratch.allocate(2 * n);
1,070✔
562
        std::ranges::fill(ones, max_limb);
1,070✔
563
        const std::span<uint_multiprecision_t> q = scratch.allocate(n + 1);
1,070✔
564
        const std::span<uint_multiprecision_t> r = scratch.allocate(2 * n + 1);
1,070✔
565
        divide_unsigned(q, r, ones, d, scratch);
1,070✔
566
        BEMAN_BIG_INT_DEBUG_ASSERT(q[n] == 1);
1,070✔
567
        std::ranges::copy(q.first(n), inverse.begin());
1,070✔
568
        scratch.deallocate(2 * n + 1);
1,070✔
569
        scratch.deallocate(n + 1);
1,070✔
570
        scratch.deallocate(2 * n);
1,070✔
571
        return;
1,070✔
572
    }
573

574
    // Recursive top-half reciprocal, built directly into the high limbs of
575
    // the output: the candidate is X_h * B^{n-h} plus a low correction.
576
    const std::size_t h = (n + 1) / 2;
2,337✔
577
    const std::size_t l = n - h;
2,337✔
578
    reciprocal_span(inverse.subspan(l), d.subspan(l), scratch, threshold_override);
2,337✔
579
    std::ranges::fill(inverse.first(l), uint_multiprecision_t{0});
2,337✔
580

581
    // T = d * X_h = d * I_h + d * B^h over n + h + 1 limbs.
582
    const std::size_t                      t_len = n + h + 1;
2,337✔
583
    const std::span<uint_multiprecision_t> t     = scratch.allocate(t_len);
2,337✔
584
    std::ranges::fill(t, uint_multiprecision_t{0});
2,337✔
585
    multiply_runtime_any(t.first(n + h), d, inverse.subspan(l), scratch.heap());
2,337✔
586
    add_shifted(t, h, d);
2,337✔
587

588
    // E = B^{n+h} - T, |E| < 2 * B^n; T's limb n+h is 0 or 1, deciding the
589
    // sign. Reuse t's buffer for |E|.
590
    BEMAN_BIG_INT_DEBUG_ASSERT(t[n + h] <= 1);
2,337✔
591
    const bool e_negative = t[n + h] != 0;
2,337✔
592
    if (e_negative) {
2,337✔
593
        // |E| = T - B^{n+h}: dropping the top limb is the whole subtraction.
594
        t[n + h] = 0;
1,618✔
595
    } else {
596
        // |E| = B^{n+h} - T = (all-ones - T) + 1; T > 0 because d != 0.
597
        for (std::size_t i = 0; i < n + h; ++i) {
61,543✔
598
            t[i] = max_limb - t[i];
60,824✔
599
        }
600
        const bool carry = increment_span(t.first(n + h));
719✔
601
        BEMAN_BIG_INT_DEBUG_ASSERT(!carry);
719✔
602
    }
603
    const std::size_t e_size = trimmed_size_span(t.first(n + h));
2,337✔
604
    BEMAN_BIG_INT_DEBUG_ASSERT(e_size <= n + 1);
2,337✔
605
    const auto e_view = std::span<const uint_multiprecision_t>{t.data(), e_size};
2,337✔
606

607
    // Correction = floor(X_h * |E| / B^{2h}) = (I_h * E + E * B^h) >> 2h limbs.
608
    const std::size_t                      c_len = e_size + h + 1;
2,337✔
609
    const std::span<uint_multiprecision_t> c     = scratch.allocate(c_len);
2,337✔
610
    std::ranges::fill(c, uint_multiprecision_t{0});
2,337✔
611
    multiply_runtime_any(c.first(e_size + h), e_view, inverse.subspan(l), scratch.heap());
2,337✔
612
    add_shifted(c, h, e_view);
2,337✔
613
    const auto corr = c_len > 2 * h ? std::span<const uint_multiprecision_t>{c.data() + 2 * h, c_len - 2 * h}
2,337✔
614
                                    : std::span<const uint_multiprecision_t>{};
2,337✔
615

616
    if (e_negative) {
2,337✔
617
        // A borrow past the top would push X below B^n; clamp to the bottom
618
        // of the range and let the residual fix recover.
619
        if (subtract_unsigned_spans_borrow_out(inverse, inverse, corr)) {
1,618✔
NEW
620
            std::ranges::fill(inverse, uint_multiprecision_t{0});
×
621
        }
622
    } else {
623
        if (add_unsigned_spans(inverse, inverse, corr)) {
719✔
NEW
624
            std::ranges::fill(inverse, max_limb);
×
625
        }
626
    }
627
    scratch.deallocate(c_len);
2,337✔
628
    scratch.deallocate(t_len);
2,337✔
629

630
    // Exact residual fix, carried out on residues mod B^wv - 1: the true
631
    // residual R' = B^{2n} - 1 - d * X lies in (-9 * B^n, B^n), far below
632
    // the modulus, so its residue identifies its value and sign exactly,
633
    // and the wrapped product costs well under a full multiplication.
634
    const std::size_t                      wv  = multiply_mod_bnm1_next_size(n + 1, multiply_mod_bnm1_cutoff);
2,337✔
635
    const std::span<uint_multiprecision_t> v   = scratch.allocate(wv);
2,337✔
636
    const std::span<uint_multiprecision_t> res = scratch.allocate(wv);
2,337✔
637

638
    // v = d * X mod (B^wv - 1) = d * I + d * B^n; the shifted part is a
639
    // carry-free rotation of d's limbs within the wrap.
640
    multiply_mod_bnm1(v, d, std::span<const uint_multiprecision_t>{inverse.data(), n}, scratch);
2,337✔
641
    std::ranges::fill(res, uint_multiprecision_t{0});
2,337✔
642
    for (std::size_t i = 0; i < n; ++i) {
202,113✔
643
        res[(n + i) % wv] = d[i];
199,776✔
644
    }
645
    add_mod_bnm1(v, std::span<const uint_multiprecision_t>{res.data(), wv});
2,337✔
646

647
    // target = (B^{2n} - 1) mod (B^wv - 1) = B^{(2n) mod wv} - 1: a run of
648
    // all-ones limbs.
649
    const std::size_t r2 = (2 * n) % wv;
2,337✔
650
    std::ranges::fill(res.first(r2), max_limb);
2,337✔
651
    std::ranges::fill(res.subspan(r2), uint_multiprecision_t{0});
2,337✔
652

653
    // res = (target - v) mod (B^wv - 1), congruent to R'.
654
    if (subtract_unsigned_spans_borrow_out(res, res, std::span<const uint_multiprecision_t>{v.data(), wv})) {
2,337✔
655
        // Wrapped past zero: -B^wv == -1 (mod B^wv - 1).
656
        [[maybe_unused]] const bool all_zero = decrement_span(res);
2,238✔
657
    }
658

659
    // Sign disambiguation by range: an undershooting candidate leaves a
660
    // non-negative residual of at most ~11 * B^n (limb n at most 11), while
661
    // an overshooting one maps to at least B^wv - 1 - 10 * B^n, whose limbs
662
    // above n are saturated (or, when wv == n + 1, whose limb n is within
663
    // 11 of the limb maximum). 64 splits the gap with room to spare.
664
    const auto is_negative = [&]() { return !is_span_zero(res.subspan(n + 1)) || res[n] > 64; };
3,507✔
665

666
    [[maybe_unused]] int fixes = 0;
2,337✔
667
    while (is_negative()) {
3,507✔
668
        const bool borrow = decrement_span(inverse);
1,170✔
669
        BEMAN_BIG_INT_DEBUG_ASSERT(!borrow);
1,170✔
670
        add_mod_bnm1(res, d);
1,170✔
671
        ++fixes;
1,170✔
672
        BEMAN_BIG_INT_DEBUG_ASSERT(fixes <= 16);
1,170✔
673
    }
674
    while (compare_unsigned_spans(res, d) != std::strong_ordering::less) {
2,614✔
675
        const bool carry = increment_span(inverse);
277✔
676
        BEMAN_BIG_INT_DEBUG_ASSERT(!carry);
277✔
677
        subtract_unsigned_spans(res, res, d);
277✔
678
        ++fixes;
277✔
679
        BEMAN_BIG_INT_DEBUG_ASSERT(fixes <= 16);
277✔
680
    }
681
    scratch.deallocate(wv);
2,337✔
682
    scratch.deallocate(wv);
2,337✔
683
}
684

685
// ---------------------------------------------------------------------------
686
// Block-wise Barrett division driver. Same outer contract as divide_unsigned
687
// (trimmed inputs, divisor.size() >= 2, quotient/remainder spans fully
688
// written). Normalizes by the divisor's leading-zero count, computes the
689
// exact reciprocal X = B^n + I once, then marches n-limb dividend windows
690
// from the top down exactly like divide_burnikel_ziegler -- but each
691
// window's quotient block costs two multiplications:
692
//   q_hat = U_hi + high_half(U_hi * I),   R = U - q_hat * d_hat.
693
// With the exact reciprocal, q_hat never overestimates and undershoots by at
694
// most 4: q_hat = floor(U_hi*X/B^n) <= floor(U*X/B^2n) <= floor(U/d); and
695
// q_hat > U*X/B^2n - X/B^n - 1 > U/d - 4 since X < 2*B^n and
696
// X > B^2n/d - 1. The correction loop adds d back accordingly.
697
// `invert_override` forwards to reciprocal_span (test-only escape hatch).
698
// ---------------------------------------------------------------------------
699
void divide_barrett(const std::span<uint_multiprecision_t>       quotient,
710✔
700
                    const std::span<uint_multiprecision_t>       remainder,
701
                    const std::span<const uint_multiprecision_t> dividend,
702
                    const std::span<const uint_multiprecision_t> divisor,
703
                    scratch_allocator_base&                      scratch,
704
                    const std::size_t                            invert_override) {
705
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.size() >= 2);
710✔
706
    BEMAN_BIG_INT_DEBUG_ASSERT(divisor.back() != 0);
710✔
707
    BEMAN_BIG_INT_DEBUG_ASSERT(!dividend.empty());
710✔
708
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.back() != 0);
710✔
709
    BEMAN_BIG_INT_DEBUG_ASSERT(dividend.size() >= divisor.size());
710✔
710
    BEMAN_BIG_INT_DEBUG_ASSERT(quotient.size() >= dividend.size() - divisor.size() + 1);
710✔
711
    BEMAN_BIG_INT_DEBUG_ASSERT(remainder.size() >= dividend.size() + 1);
710✔
712

713
    const std::size_t n = divisor.size();
710✔
714
    const std::size_t m = dividend.size();
710✔
715
    const std::size_t t = barrett_blocks(dividend, divisor);
710✔
716

717
    const unsigned shift = static_cast<unsigned>(std::countl_zero(divisor.back()));
710✔
718

719
    // Normalized divisor view (top bit set).
720
    std::span<const uint_multiprecision_t> d = divisor;
710✔
721
    if (shift != 0) {
710✔
722
        const std::span<uint_multiprecision_t> d_norm = scratch.allocate(n);
347✔
723
        std::ranges::copy(divisor, d_norm.begin());
347✔
724
        const std::size_t d_size = shift_left_n(d_norm, n, shift);
347✔
725
        BEMAN_BIG_INT_DEBUG_ASSERT(d_size == n);
347✔
726
        d = d_norm.first(d_size);
347✔
727
    }
728

729
    // w = dividend << shift, zero-extended to t blocks of n limbs.
730
    const std::span<uint_multiprecision_t> w = scratch.allocate(t * n);
710✔
731
    std::ranges::fill(w, uint_multiprecision_t{0});
710✔
732
    std::ranges::copy(dividend, w.begin());
710✔
733
    if (shift != 0) {
710✔
734
        const std::size_t w_size = shift_left_n(w, m, shift);
347✔
735
        BEMAN_BIG_INT_DEBUG_ASSERT(w_size <= t * n);
347✔
736
    }
737

738
    const std::span<uint_multiprecision_t> q_work = scratch.allocate((t - 1) * n);
710✔
739

740
    // The exact reciprocal of the normalized divisor; its computation scratch
741
    // rewinds before the standing block products below are allocated.
742
    const std::span<uint_multiprecision_t> inv = scratch.allocate(n);
710✔
743
    reciprocal_span(inv, d, scratch, invert_override);
710✔
744
    const auto inv_view = std::span<const uint_multiprecision_t>{inv.data(), inv.size()};
710✔
745

746
    // The estimate product stays full (its high half is needed exactly); the
747
    // q_hat * d_hat subtrahend only matters mod B^wrap - 1 because the true
748
    // R = U - q_hat * d_hat is known to be below 5 * B^n < B^wrap - 1.
749
    const std::size_t                      wrap     = multiply_mod_bnm1_next_size(n + 1, multiply_mod_bnm1_cutoff);
710✔
750
    const std::span<uint_multiprecision_t> p        = scratch.allocate(2 * n);
710✔
751
    const std::span<uint_multiprecision_t> tw       = scratch.allocate(wrap);
710✔
752
    const std::span<uint_multiprecision_t> uf       = scratch.allocate(wrap);
710✔
753
    constexpr uint_multiprecision_t        max_limb = ~uint_multiprecision_t{0};
710✔
754

755
    // March the windows from the top down; window i leaves its remainder in
756
    // w[i*n..(i+1)*n), the high half of window i-1.
757
    for (std::size_t i = t - 1; i-- > 0;) {
3,300✔
758
        const std::span<uint_multiprecision_t>       window = w.subspan(i * n, 2 * n);
2,590✔
759
        const std::span<const uint_multiprecision_t> u_hi{window.data() + n, n};
2,590✔
760
        const std::span<uint_multiprecision_t>       q_block = q_work.subspan(i * n, n);
2,590✔
761

762
        // q_hat = U_hi + high_half(U_hi * X) where X = B^n + I.
763
        std::ranges::fill(p, uint_multiprecision_t{0});
2,590✔
764
        multiply_runtime_any(p, u_hi, inv_view, scratch.heap());
2,590✔
765
        const bool q_carry =
766
            add_unsigned_spans(q_block, u_hi, std::span<const uint_multiprecision_t>{p.data() + n, n});
2,590✔
767
        BEMAN_BIG_INT_DEBUG_ASSERT(!q_carry);
2,590✔
768

769
        // R = (U - q_hat * d_hat) mod (B^wrap - 1), recovered exactly from
770
        // its residue: tw = the wrapped subtrahend, uf = the folded window.
771
        multiply_mod_bnm1(tw, std::span<const uint_multiprecision_t>{q_block.data(), n}, d, scratch);
2,590✔
772
        fold_mod_bnm1(uf, std::span<const uint_multiprecision_t>{window.data(), 2 * n});
2,590✔
773
        if (subtract_unsigned_spans_borrow_out(uf, uf, std::span<const uint_multiprecision_t>{tw.data(), wrap})) {
2,590✔
774
            // Wrapped past zero: -B^wrap == -1 (mod B^wrap - 1).
775
            [[maybe_unused]] const bool all_zero = decrement_span(uf);
2✔
776
        }
777
        if (uf.front() == max_limb &&
2,623✔
778
            std::ranges::all_of(uf, [](const uint_multiprecision_t x) { return x == max_limb; })) {
213✔
779
            // The all-ones pattern equals the modulus and means R = 0.
NEW
780
            std::ranges::fill(uf, uint_multiprecision_t{0});
×
781
        }
782
        BEMAN_BIG_INT_DEBUG_ASSERT(wrap >= n + 1 && wrap <= 2 * n);
2,590✔
783
        BEMAN_BIG_INT_DEBUG_ASSERT(is_span_zero(uf.subspan(n + 1)));
2,590✔
784
        BEMAN_BIG_INT_DEBUG_ASSERT(uf[n] <= 4);
2,590✔
785

786
        // Write R back into the window (it fits n + 1 limbs with the top at
787
        // most 4 before corrections).
788
        std::ranges::copy(uf, window.begin());
2,590✔
789
        std::ranges::fill(window.subspan(wrap), uint_multiprecision_t{0});
2,590✔
790

791
        // q - q_hat <= 4: add the divisor back accordingly.
792
        [[maybe_unused]] int corrections = 0;
2,590✔
793
        while (compare_unsigned_spans(window, d) != std::strong_ordering::less) {
4,649✔
794
            const bool carry = increment_span(q_block);
2,059✔
795
            BEMAN_BIG_INT_DEBUG_ASSERT(!carry);
2,059✔
796
            subtract_unsigned_spans(window, window, d);
2,059✔
797
            ++corrections;
2,059✔
798
            BEMAN_BIG_INT_DEBUG_ASSERT(corrections <= 4);
2,059✔
799
        }
800
        BEMAN_BIG_INT_DEBUG_ASSERT(is_span_zero(window.subspan(n)));
2,590✔
801
    }
802

803
    scratch.deallocate(wrap);
710✔
804
    scratch.deallocate(wrap);
710✔
805
    scratch.deallocate(2 * n);
710✔
806

807
    // Quotient blocks concatenate exactly; trim and copy out.
808
    const std::size_t q_size = trimmed_size_span(q_work);
710✔
809
    BEMAN_BIG_INT_DEBUG_ASSERT(q_size <= quotient.size());
710✔
810
    std::ranges::copy(q_work.first(q_size), quotient.begin());
710✔
811
    std::ranges::fill(quotient.subspan(q_size), uint_multiprecision_t{0});
710✔
812

813
    // Remainder: undo the normalization shift on the final window's low half.
814
    if (shift != 0) {
710✔
815
        const uint_multiprecision_t dropped = shift_right_n(w.first(n), shift);
347✔
816
        BEMAN_BIG_INT_DEBUG_ASSERT(dropped == 0);
347✔
817
    }
818
    std::ranges::copy(w.first(n), remainder.begin());
710✔
819
    std::ranges::fill(remainder.subspan(n), uint_multiprecision_t{0});
710✔
820
}
710✔
821

822
// Convenience overload: sizes and owns the workspace, then forwards to the
823
// scratch-based driver above.
824

825
} // namespace beman::big_int::detail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc