Skip to main content

dashu_int/
div_exact.rs

1//! Exact division — the quotient of `self / other`, or `None` when the division is not exact.
2//!
3//! The [`DivExact`] / [`DivExactAssign`] traits (re-exported through `dashu-base` from
4//! `num-modular`, with the empty precomputation `()`) compute `self / other` as `Some(q)` when
5//! `other | self`, `None` otherwise.
6//!
7//! Exact division uses **Hensel (2-adic) division**: the modular inverse of the (odd part of the)
8//! divisor is precomputed by Newton iteration, and each quotient limb is then `(word − carry) ·
9//! d^{-1} mod 2^WORD_BITS` — a low-to-high loop of multiplies and subtracts with no normalization
10//! and no reciprocal precomputation, which makes it roughly twice as fast as a general division.
11//! The quotient is written in place into the dividend's own buffer, so no scratch is allocated.
12//! The dividend's own buffer is used for the quotient, so `div_exact` consumes its dividend (the
13//! assigning forms take a read-only divisibility probe — or a backup clone — to leave the dividend
14//! untouched on failure).
15//!
16//! Three divisor widths are supported by dedicated kernels:
17//!
18//! - a **single word** ([`hensel_div_odd_in_place`]): each step subtracts one `q · d` product;
19//! - a **double word** ([`hensel_div_odd_dword_in_place`]): each step subtracts `q · d` over a
20//!   two-word window via the double-word multiply kernel;
21//! - **multi word** ([`hensel_div_exact_large`]): each step subtracts `q · D` over a `D`-length
22//!   window.
23//!
24//! Divisors are stripped of their factors of 2 first (the quotient is then shifted back), so the
25//! kernels only ever see an odd divisor. The divisibility test ([`UBig::is_multiple_of`]) reuses
26//! the same kernels: a read-only probe for single-word divisors, and the exactness test of the
27//! division itself (on a scratch copy) for wider divisors.
28//!
29//! The multi-word kernel is schoolbook (O(n·m)), so for divisors beyond
30//! [`THRESHOLD_DIV_EXACT_DEFAULT`] words — where the general division's sub-quadratic
31//! divide-and-conquer algorithm is faster — exact division falls back to the general division plus
32//! a remainder check.
33
34use dashu_base::{DivRem, Sign, UnsignedAbs};
35use num_modular::{DivExact, DivExactAssign};
36
37use crate::{
38    add,
39    arch::word::{DoubleWord, Word},
40    ibig::IBig,
41    math::inv_mod_pow2,
42    mul::{sub_mul_dword_same_len_in_place, sub_mul_word_same_len_in_place},
43    primitive::{extend_word, shrink_dword, WORD_BITS},
44    repr::{TypedRepr, TypedReprRef},
45    ubig::UBig,
46};
47
48/// If the divisor length (in words) exceeds this, exact division falls back to the general
49/// division — the schoolbook Hensel loop (O(n·m)) loses to the sub-quadratic divide-and-conquer
50/// division at that size. The crossover is a heuristic (it depends on the size ratio as well as
51/// the absolute divisor size); 180 words matches the observed crossover for balanced operands.
52const THRESHOLD_DIV_EXACT_DEFAULT: usize = 180;
53
54/// Environment-variable override for the exact-division threshold.
55///
56/// When the `tuning` feature is active the user may set `DASHU_THRESHOLD_DIV_EXACT` to override
57/// the compile-time default.
58mod threshold {
59    #[inline]
60    pub fn div_exact() -> usize {
61        #[cfg(feature = "tuning")]
62        {
63            if let Ok(s) = std::env::var("DASHU_THRESHOLD_DIV_EXACT") {
64                if let Ok(v) = s.parse::<usize>() {
65                    return v;
66                }
67            }
68        }
69        super::THRESHOLD_DIV_EXACT_DEFAULT
70    }
71}
72
73impl UBig {
74    /// In-place exact division by a fixed `DoubleWord` divisor: `self` becomes `self / divisor`
75    /// when `divisor | self`, and is left unchanged otherwise. Returns whether the division was
76    /// exact.
77    ///
78    /// The in-place backend of [`DivExactAssign`] for a `DoubleWord` divisor (mirroring
79    /// [`UBig::is_multiple_of_const`], the `const` divisor form). A single-word divisor is probed
80    /// by the read-only Hensel test first (so a failure leaves `self` untouched) and then divided
81    /// in place; a double-word divisor backs up `self` with an `O(len)` clone (its probe is as
82    /// expensive as the division itself).
83    fn div_exact_assign_dword(&mut self, divisor: DoubleWord) -> bool {
84        if divisor == 0 {
85            return false; // 0 is not a divisor
86        }
87        if self.is_zero() || divisor == 1 {
88            return true; // 0 / d = 0, self / 1 = self
89        }
90        if shrink_dword(divisor).is_some() {
91            // A single-word divisor: probe first (cheap, read-only), so the in-place division
92            // below is guaranteed to succeed and `self` can be consumed without a backup.
93            if !self.repr().is_multiple_of(TypedReprRef::RefSmall(divisor)) {
94                return false;
95            }
96            let taken = core::mem::take(self);
97            let q = taken
98                .into_repr()
99                .div_exact(TypedRepr::Small(divisor), &())
100                .expect("the probe passed, so the division is exact");
101            *self = UBig(q);
102            return true;
103        }
104        // A double-word divisor: back up `self` (an O(len) clone) so a failed division can
105        // restore it.
106        let backup = self.clone();
107        let taken = core::mem::take(self);
108        match taken.into_repr().div_exact(TypedRepr::Small(divisor), &()) {
109            Some(q) => {
110                *self = UBig(q);
111                true
112            }
113            None => {
114                *self = backup;
115                false
116            }
117        }
118    }
119}
120
121/// Ops for `TypedRepr` / `TypedReprRef` — the four ownership combinations, mirroring
122/// [`div_ops`](crate::div_ops), together with the `Buffer`-level helpers they dispatch to (the
123/// Hensel kernels live at the top level).
124pub(crate) mod repr {
125    use super::*;
126    use crate::{
127        arch::word::{DoubleWord, Word},
128        buffer::Buffer,
129        div,
130        math::inv_mod_pow2,
131        primitive::{extend_word, shrink_dword, split_dword, WORD_BITS, WORD_BITS_USIZE},
132        repr::{Repr, TypedRepr, TypedReprRef},
133        shift,
134        ubig::UBig,
135    };
136
137    impl DivExact<TypedRepr, ()> for TypedRepr {
138        type Output = Repr;
139
140        #[inline]
141        fn div_exact(self, rhs: TypedRepr, _: &()) -> Option<Repr> {
142            match (self, rhs) {
143                (TypedRepr::Small(dword0), TypedRepr::Small(dword1)) => {
144                    div_exact_dword(dword0, dword1)
145                }
146                (TypedRepr::Small(_), TypedRepr::Large(_)) => None, // small < large, cannot divide
147                (TypedRepr::Large(buffer0), TypedRepr::Small(dword1)) => {
148                    if let Some(word) = shrink_dword(dword1) {
149                        div_exact_large_word(buffer0, word)
150                    } else {
151                        div_exact_large_dword(buffer0, dword1)
152                    }
153                }
154                (TypedRepr::Large(buffer0), TypedRepr::Large(buffer1)) => {
155                    div_exact_large(buffer0, buffer1)
156                }
157            }
158        }
159    }
160
161    impl<'l> DivExact<TypedRepr, ()> for TypedReprRef<'l> {
162        type Output = Repr;
163
164        #[inline]
165        fn div_exact(self, rhs: TypedRepr, _: &()) -> Option<Repr> {
166            match (self, rhs) {
167                (TypedReprRef::RefSmall(dword0), TypedRepr::Small(dword1)) => {
168                    div_exact_dword(dword0, dword1)
169                }
170                (TypedReprRef::RefSmall(_), TypedRepr::Large(_)) => None,
171                (TypedReprRef::RefLarge(words0), TypedRepr::Small(dword1)) => {
172                    if let Some(word) = shrink_dword(dword1) {
173                        div_exact_large_word(words0.into(), word)
174                    } else {
175                        div_exact_large_dword(words0.into(), dword1)
176                    }
177                }
178                (TypedReprRef::RefLarge(words0), TypedRepr::Large(buffer1)) => {
179                    div_exact_large(words0.into(), buffer1)
180                }
181            }
182        }
183    }
184
185    impl<'r> DivExact<TypedReprRef<'r>, ()> for TypedRepr {
186        type Output = Repr;
187
188        #[inline]
189        fn div_exact(self, rhs: TypedReprRef, _: &()) -> Option<Repr> {
190            match (self, rhs) {
191                (TypedRepr::Small(dword0), TypedReprRef::RefSmall(dword1)) => {
192                    div_exact_dword(dword0, dword1)
193                }
194                (TypedRepr::Small(_), TypedReprRef::RefLarge(_)) => None,
195                (TypedRepr::Large(buffer0), TypedReprRef::RefSmall(dword1)) => {
196                    if let Some(word) = shrink_dword(dword1) {
197                        div_exact_large_word(buffer0, word)
198                    } else {
199                        div_exact_large_dword(buffer0, dword1)
200                    }
201                }
202                (TypedRepr::Large(buffer0), TypedReprRef::RefLarge(words1)) => {
203                    div_exact_large(buffer0, words1.into())
204                }
205            }
206        }
207    }
208
209    impl<'l, 'r> DivExact<TypedReprRef<'r>, ()> for TypedReprRef<'l> {
210        type Output = Repr;
211
212        #[inline]
213        fn div_exact(self, rhs: TypedReprRef, _: &()) -> Option<Repr> {
214            match (self, rhs) {
215                (TypedReprRef::RefSmall(dword0), TypedReprRef::RefSmall(dword1)) => {
216                    div_exact_dword(dword0, dword1)
217                }
218                (TypedReprRef::RefSmall(_), TypedReprRef::RefLarge(_)) => None,
219                (TypedReprRef::RefLarge(words0), TypedReprRef::RefSmall(dword1)) => {
220                    if let Some(word) = shrink_dword(dword1) {
221                        div_exact_large_word(words0.into(), word)
222                    } else {
223                        div_exact_large_dword(words0.into(), dword1)
224                    }
225                }
226                (TypedReprRef::RefLarge(words0), TypedReprRef::RefLarge(words1)) => {
227                    div_exact_large(words0.into(), words1.into())
228                }
229            }
230        }
231    }
232
233    /// Both operands fit in a `DoubleWord`: the division is trivial.
234    #[inline]
235    fn div_exact_dword(lhs: DoubleWord, rhs: DoubleWord) -> Option<Repr> {
236        if rhs == 0 {
237            None
238        } else if rhs == 1 {
239            Some(Repr::from_dword(lhs))
240        } else if lhs % rhs == 0 {
241            Some(Repr::from_dword(lhs / rhs))
242        } else {
243            None
244        }
245    }
246
247    /// In-place exact division of the `Buffer` by a single word.
248    ///
249    /// The dividend buffer is consumed and replaced by the quotient on success (the caller owns the
250    /// buffer, so a failed division simply drops the modified buffer). The divisor's power-of-two part
251    /// is stripped by the 2-valuation, and the odd part is divided out by the Hensel kernel.
252    fn div_exact_large_word(mut buffer: Buffer, d: Word) -> Option<Repr> {
253        if d == 0 {
254            return None; // 0 is not a divisor
255        }
256        if d == 1 {
257            return Some(Repr::from_buffer(buffer));
258        }
259        let trailing = d.trailing_zeros();
260        let d_odd = d >> trailing;
261        if d_odd == 1 {
262            // d is a power of two: exact iff the 2-valuation supplies enough twos.
263            if trailing_zeros(&buffer) >= trailing as usize {
264                shift::shr_in_place(&mut buffer, trailing);
265                return Some(Repr::from_buffer(buffer));
266            }
267            return None;
268        }
269        if trailing > 0 && trailing_zeros(&buffer) < trailing as usize {
270            return None;
271        }
272        let di = inv_mod_pow2(extend_word(d_odd), WORD_BITS) as Word;
273        if !hensel_div_odd_in_place(&mut buffer, d_odd, di) {
274            return None;
275        }
276        if trailing > 0 {
277            shift::shr_in_place(&mut buffer, trailing);
278        }
279        Some(Repr::from_buffer(buffer))
280    }
281
282    /// In-place exact division of the `Buffer` by a double word.
283    ///
284    /// Like [`div_exact_large_word`], but for a divisor that needs two words: the odd part is divided
285    /// by the double-word Hensel kernel ([`hensel_div_odd_dword_in_place`]), or by the single-word
286    /// kernel when the odd part fits in a word (a divisor with a large power-of-two part).
287    fn div_exact_large_dword(mut buffer: Buffer, d: DoubleWord) -> Option<Repr> {
288        debug_assert!(shrink_dword(d).is_none()); // the caller dispatches on the width
289        let trailing = d.trailing_zeros();
290        let d_odd = d >> trailing;
291        if d_odd == 1 {
292            // d is a power of two: exact iff the 2-valuation supplies enough twos.
293            if trailing_zeros(&buffer) >= trailing as usize {
294                shr_erase_front(&mut buffer, trailing as usize);
295                return Some(Repr::from_buffer(buffer));
296            }
297            return None;
298        }
299        if trailing > 0 && trailing_zeros(&buffer) < trailing as usize {
300            return None;
301        }
302        if let Some(word) = shrink_dword(d_odd) {
303            let di = inv_mod_pow2(extend_word(word), WORD_BITS) as Word;
304            if !hensel_div_odd_in_place(&mut buffer, word, di) {
305                return None;
306            }
307        } else {
308            let (d_lo, d_hi) = split_dword(d_odd);
309            let di = inv_mod_pow2(extend_word(d_lo), WORD_BITS) as Word;
310            if !hensel_div_odd_dword_in_place(&mut buffer, d_lo, d_hi, di) {
311                return None;
312            }
313        }
314        if trailing > 0 {
315            shr_erase_front(&mut buffer, trailing as usize);
316        }
317        Some(Repr::from_buffer(buffer))
318    }
319
320    /// In-place exact division of the `Buffer` by a multi-word divisor.
321    ///
322    /// For a divisor within [`THRESHOLD_DIV_EXACT_DEFAULT`] words, the common factors of 2 are
323    /// stripped first (both buffers are shifted and trimmed), so the Hensel kernel sees an odd divisor;
324    /// the quotient needs no post-shift because the dividend was shifted before the division. A divisor
325    /// that collapses to one or two words after the strip is handed to the matching narrower kernel.
326    /// For a larger divisor the schoolbook Hensel loop (O(n·m)) loses to the general division — which
327    /// switches to a sub-quadratic divide-and-conquer algorithm at large sizes — so the general
328    /// division is used instead.
329    fn div_exact_large(mut dividend: Buffer, mut divisor: Buffer) -> Option<Repr> {
330        if dividend.len() < divisor.len() {
331            return None; // dividend is smaller than the divisor
332        }
333        if divisor.len() > super::threshold::div_exact() {
334            // General division + remainder check; same result, faster for large divisors.
335            let (q, r) =
336                UBig(Repr::from_buffer(dividend)).div_rem(UBig(Repr::from_buffer(divisor)));
337            return if r.is_zero() { Some(q.0) } else { None };
338        }
339        let s = trailing_zeros(&divisor);
340        if s > 0 {
341            if trailing_zeros(&dividend) < s {
342                return None; // not enough factors of 2 in the dividend
343            }
344            shr_erase_front(&mut dividend, s);
345            shr_erase_front(&mut divisor, s);
346            divisor.pop_zeros();
347            dividend.pop_zeros();
348        }
349        if dividend.len() < divisor.len() {
350            return None; // dividend is smaller than the divisor
351        }
352        match divisor.len() {
353            1 => {
354                // A divisor like 2^s·3 with a large power-of-two part collapses to one word.
355                let d = divisor[0];
356                debug_assert!(d & 1 == 1, "the common factors of 2 were already stripped");
357                let di = inv_mod_pow2(extend_word(d), WORD_BITS) as Word;
358                if !hensel_div_odd_in_place(&mut dividend, d, di) {
359                    return None;
360                }
361            }
362            2 => {
363                let (d_lo, d_hi) = (divisor[0], divisor[1]);
364                debug_assert!(d_lo & 1 == 1, "the common factors of 2 were already stripped");
365                let di = inv_mod_pow2(extend_word(d_lo), WORD_BITS) as Word;
366                if !hensel_div_odd_dword_in_place(&mut dividend, d_lo, d_hi, di) {
367                    return None;
368                }
369            }
370            _ => {
371                if !hensel_div_exact_large(&mut dividend, &divisor) {
372                    return None;
373                }
374            }
375        }
376        Some(Repr::from_buffer(dividend))
377    }
378
379    impl TypedReprRef<'_> {
380        /// Determine whether `self` is a multiple of `rhs` (non-const; the const counterpart is
381        /// [`TypedReprRef::is_multiple_of_dword`]).
382        ///
383        /// A single-word divisor uses the read-only Hensel divisibility test (multiply-based, no
384        /// remainder computation); wider divisors reuse the exactness test of the Hensel division
385        /// itself on a scratch copy.
386        pub(crate) fn is_multiple_of(&self, rhs: TypedReprRef) -> bool {
387            match (self, rhs) {
388                (TypedReprRef::RefSmall(dword0), TypedReprRef::RefSmall(dword1)) => {
389                    dword1 != 0 && dword0 % dword1 == 0
390                }
391                (TypedReprRef::RefSmall(_), TypedReprRef::RefLarge(_)) => false,
392                (TypedReprRef::RefLarge(words0), TypedReprRef::RefSmall(dword1)) => {
393                    is_multiple_of_dword(words0, dword1)
394                }
395                (TypedReprRef::RefLarge(words0), TypedReprRef::RefLarge(words1)) => {
396                    is_multiple_of_large(words0, words1)
397                }
398            }
399        }
400    }
401
402    /// Is `words` a multiple of the single word `d`?
403    fn is_multiple_of_word(words: &[Word], d: Word) -> bool {
404        if d == 0 {
405            return false; // 0 is not a divisor
406        }
407        let trailing = d.trailing_zeros();
408        let d_odd = d >> trailing;
409        if d_odd == 1 {
410            // d is a power of two: exact iff the 2-valuation supplies enough twos.
411            return trailing_zeros(words) >= trailing as usize;
412        }
413        if trailing > 0 && trailing_zeros(words) < trailing as usize {
414            return false;
415        }
416        let di = inv_mod_pow2(extend_word(d_odd), WORD_BITS) as Word;
417        hensel_is_multiple_of(words, d_odd, di)
418    }
419
420    /// Is `words` a multiple of the double word `d`? A divisor that fits in a word is delegated to
421    /// [`is_multiple_of_word`]; a full double word runs the read-only divisibility test via the
422    /// exactness check of the double-word Hensel division on a scratch copy.
423    fn is_multiple_of_dword(words: &[Word], d: DoubleWord) -> bool {
424        if d == 0 {
425            return false; // 0 is not a divisor
426        }
427        if let Some(word) = shrink_dword(d) {
428            return is_multiple_of_word(words, word);
429        }
430        let trailing = d.trailing_zeros();
431        let d_odd = d >> trailing;
432        if d_odd == 1 {
433            return trailing_zeros(words) >= trailing as usize;
434        }
435        if trailing > 0 && trailing_zeros(words) < trailing as usize {
436            return false;
437        }
438        let mut buffer = words.to_vec();
439        if let Some(word) = shrink_dword(d_odd) {
440            // The odd part fits in a word (e.g. 5·2^70): the divisor is effectively a single word,
441            // so the single-word kernel is required (the double-word kernel would produce a quotient
442            // that is one word short).
443            let di = inv_mod_pow2(extend_word(word), WORD_BITS) as Word;
444            hensel_div_odd_in_place(&mut buffer, word, di)
445        } else {
446            let (d_lo, d_hi) = split_dword(d_odd);
447            let di = inv_mod_pow2(extend_word(d_lo), WORD_BITS) as Word;
448            hensel_div_odd_dword_in_place(&mut buffer, d_lo, d_hi, di)
449        }
450    }
451
452    /// Is `words` a multiple of the multi-word `divisor`?
453    ///
454    /// The exactness test of the Hensel division on a scratch copy IS the divisibility test; sharing
455    /// [`div_exact_large`] also shares the common-factor stripping.
456    fn is_multiple_of_large(words: &[Word], divisor: &[Word]) -> bool {
457        div_exact_large(Buffer::from(words), Buffer::from(divisor)).is_some()
458    }
459
460    /// The number of trailing zero bits of the value stored in `words` (little-endian). Returns
461    /// `usize::MAX` for the all-zero value (every number divides it).
462    fn trailing_zeros(words: &[Word]) -> usize {
463        for (i, &w) in words.iter().enumerate() {
464            if w != 0 {
465                return i * WORD_BITS_USIZE + w.trailing_zeros() as usize;
466            }
467        }
468        usize::MAX
469    }
470
471    /// Right-shift the buffer by `shift` bits in place, erasing the whole words that fall out.
472    ///
473    /// [`shift::shr_in_place`] only handles shifts within one word, so larger shifts (a divisor with
474    /// whole words of trailing zeros) first erase the low words, then shift the remainder.
475    fn shr_erase_front(buffer: &mut Buffer, shift: usize) {
476        buffer.erase_front(shift / WORD_BITS_USIZE);
477        if shift % WORD_BITS_USIZE != 0 {
478            shift::shr_in_place(buffer, (shift % WORD_BITS_USIZE) as u32);
479        }
480    }
481
482    /// A `const`-capable divisibility test for a `DoubleWord` divisor (the backend of
483    /// [`UBig::is_multiple_of_const`] / [`IBig::is_multiple_of_const`]). This is the remainder-based
484    /// test, kept separate from the non-const Hensel-based [`TypedReprRef::is_multiple_of`] because
485    /// `const fn`s cannot allocate or call non-const kernels.
486    impl<'a> TypedReprRef<'a> {
487        pub(crate) const fn is_multiple_of_dword(self, divisor: DoubleWord) -> bool {
488            if let Some(w) = shrink_dword(divisor) {
489                match self {
490                    TypedReprRef::RefSmall(dword) => dword % extend_word(w) == 0,
491                    TypedReprRef::RefLarge(words) => div::rem_by_word(words, w) == 0,
492                }
493            } else {
494                match self {
495                    TypedReprRef::RefSmall(dword) => dword % divisor == 0,
496                    TypedReprRef::RefLarge(words) => div::rem_by_dword(words, divisor) == 0,
497                }
498            }
499        }
500    }
501}
502
503/// Hensel (2-adic) division of `words` by the odd single-word `d` **in place**, using the
504/// precomputed inverse `di = d^{-1} mod 2^WORD_BITS`. Returns whether the division is exact; on
505/// success `words` holds the exact quotient.
506///
507/// Each quotient limb is `(words[i] − carry) · di mod 2^W`, computed low-to-high with only
508/// multiplies and subtracts — no normalization, no division. The computation is naturally in place:
509/// each input limb is read before its output limb is written. The exactness test comes from the top
510/// carry: the computation maintains `u = q·d + T·2^(W·n)` with `T = c + high(q[n-1]·d) ≥ 0`, so
511/// `T = 0` — i.e. `d | u`, with `q` the exact quotient — iff `c == 0` and the final high product is
512/// zero.
513pub(crate) fn hensel_div_odd_in_place(words: &mut [Word], d: Word, di: Word) -> bool {
514    let mut c: Word = 0;
515    let mut q_last = words[0].wrapping_mul(di);
516    words[0] = q_last;
517    for word in words.iter_mut().skip(1) {
518        let h = ((extend_word(q_last) * extend_word(d)) >> WORD_BITS) as Word;
519        c = c.wrapping_add(h);
520        let (l, borrow) = word.overflowing_sub(c);
521        c = borrow as Word;
522        q_last = l.wrapping_mul(di);
523        *word = q_last;
524    }
525    let h = ((extend_word(q_last) * extend_word(d)) >> WORD_BITS) as Word;
526    c == 0 && h == 0
527}
528
529/// Hensel (2-adic) divisibility test: does the odd single-word `d` divide `words`?
530///
531/// The read-only version of [`hensel_div_odd_in_place`] — the same low-to-high loop of multiplies
532/// and subtracts (each input limb read before any write), but the quotient limbs are not written
533/// out. The top-carry test is identical: `d | words` iff `c == 0` and the final high product is
534/// zero.
535pub(crate) fn hensel_is_multiple_of(words: &[Word], d: Word, di: Word) -> bool {
536    let mut c: Word = 0;
537    let mut q_last = words[0].wrapping_mul(di);
538    for word in words.iter().skip(1) {
539        let h = ((extend_word(q_last) * extend_word(d)) >> WORD_BITS) as Word;
540        c = c.wrapping_add(h);
541        let (l, borrow) = word.overflowing_sub(c);
542        c = borrow as Word;
543        q_last = l.wrapping_mul(di);
544    }
545    let h = ((extend_word(q_last) * extend_word(d)) >> WORD_BITS) as Word;
546    c == 0 && h == 0
547}
548
549/// Hensel (2-adic) division of `words` by the odd double-word `d = d_lo + d_hi·B` **in place**,
550/// using the precomputed inverse `di = d^{-1} mod 2^WORD_BITS` of the low word. Returns whether the
551/// division is exact; on success `words[..n-1]` holds the exact quotient and `words[n-1]` is zero.
552///
553/// Each step subtracts `q · d` over a two-word window via the double-word multiply kernel; the
554/// quotient limb is still a single word. The exactness test is the same as
555/// [`hensel_div_odd_in_place`]: the high part (the last word) must be zero, with no outstanding
556/// borrow.
557pub(crate) fn hensel_div_odd_dword_in_place(
558    words: &mut [Word],
559    d_lo: Word,
560    d_hi: Word,
561    di: Word,
562) -> bool {
563    let n = words.len();
564    debug_assert!(n >= 2 && d_lo & 1 == 1);
565    for i in 0..n - 1 {
566        let q = words[i].wrapping_mul(di);
567        // Subtract q·d from the 2-word window; the product q·d has at most 3 words, so the total
568        // borrow is at most one word (carry_hi == 0), spilled into the words beyond the window.
569        let (borrow_lo, borrow_hi) =
570            sub_mul_dword_same_len_in_place(&mut words[i..i + 2], &[d_lo, d_hi], q, 0);
571        debug_assert!(borrow_hi == 0, "the total borrow of q·d is at most one word");
572        if borrow_lo != 0 && (i + 2 >= n || add::sub_word_in_place(&mut words[i + 2..], borrow_lo))
573        {
574            return false; // the borrow ran off the end: the division is not exact
575        }
576        words[i] = q;
577    }
578    words[n - 1] == 0
579}
580
581/// Hensel (2-adic) exact division of `dividend` by the odd multi-word `divisor`, **in place**.
582/// Returns whether the division is exact; on success `dividend[..qn]` holds the exact quotient
583/// (`qn = dividend.len() - divisor.len() + 1`) and `dividend[qn..]` is zero.
584///
585/// The generalisation of [`hensel_div_odd_in_place`] to a multi-word divisor: each step subtracts
586/// `q · divisor` over a `divisor`-length window with [`sub_mul_word_same_len_in_place`], and the
587/// (at most one-word) borrow is propagated through the remaining words. An outstanding borrow at the
588/// end means the dividend underflowed — the division cannot be exact. For exact division the
589/// corrections cancel against `dividend[qn..]` exactly, so that suffix tests zero.
590pub(crate) fn hensel_div_exact_large(dividend: &mut [Word], divisor: &[Word]) -> bool {
591    let n = dividend.len();
592    let m = divisor.len();
593    debug_assert!(n >= m && m >= 2 && divisor[0] & 1 == 1);
594    let qn = n - m + 1;
595    let di = inv_mod_pow2(extend_word(divisor[0]), WORD_BITS) as Word;
596
597    for i in 0..qn {
598        let q = dividend[i].wrapping_mul(di);
599        // Subtract q·divisor from the window, then propagate the borrow (at most one word, since
600        // q·divisor < B^(m+1)) through the remaining words. For exact division this borrow is
601        // absorbed by the high words; if it runs off the end, the dividend underflowed.
602        let mut borrow = sub_mul_word_same_len_in_place(&mut dividend[i..i + m], q, divisor);
603        if borrow != 0 {
604            for w in dividend[i + m..].iter_mut() {
605                let (l, b) = w.overflowing_sub(borrow);
606                *w = l;
607                borrow = b as Word;
608                if borrow == 0 {
609                    break;
610                }
611            }
612        }
613        if borrow != 0 {
614            return false; // the borrow ran off the end: the division is not exact
615        }
616        dividend[i] = q;
617    }
618    dividend[qn..].iter().all(|&w| w == 0)
619}
620
621impl UBig {
622    /// Determine whether the integer is perfectly divisible by the divisor.
623    ///
624    /// A divisor that fits in a single word uses the read-only Hensel divisibility test; wider
625    /// divisors reuse the exactness test of the Hensel division.
626    ///
627    /// # Examples
628    ///
629    /// ```
630    /// # use dashu_int::UBig;
631    /// let a = UBig::from(24u8);
632    /// let b = UBig::from(6u8);
633    /// assert!(a.is_multiple_of(&b));
634    /// ```
635    ///
636    /// # Panics
637    ///
638    /// Panics if the divisor is zero.
639    #[inline]
640    pub fn is_multiple_of(&self, divisor: &Self) -> bool {
641        assert!(!divisor.is_zero(), "division by zero");
642        self.repr().is_multiple_of(divisor.repr())
643    }
644
645    /// A const version of [UBig::is_multiple_of], but only accepts [DoubleWord][crate::DoubleWord]
646    /// divisors.
647    ///
648    #[inline]
649    pub const fn is_multiple_of_const(&self, divisor: DoubleWord) -> bool {
650        self.repr().is_multiple_of_dword(divisor)
651    }
652}
653
654impl IBig {
655    /// Determine whether the integer is perfectly divisible by the divisor.
656    ///
657    /// # Examples
658    ///
659    /// ```
660    /// # use dashu_int::IBig;
661    /// let a = IBig::from(24);
662    /// let b = IBig::from(-6);
663    /// assert!(a.is_multiple_of(&b));
664    /// ```
665    ///
666    /// # Panics
667    ///
668    /// Panics if the divisor is zero.
669    #[inline]
670    pub fn is_multiple_of(&self, divisor: &Self) -> bool {
671        self.unsigned_abs().is_multiple_of(&divisor.unsigned_abs())
672    }
673
674    /// A const version of [IBig::is_multiple_of], but only accepts [DoubleWord][crate::DoubleWord]
675    /// divisors.
676    ///
677    #[inline]
678    pub const fn is_multiple_of_const(&self, divisor: DoubleWord) -> bool {
679        let (_, repr) = self.as_sign_repr();
680        repr.is_multiple_of_dword(divisor)
681    }
682}
683
684/// Trait-based exact division: the [`DivExact`] / [`DivExactAssign`] traits, re-exported through
685/// `dashu-base` from `num-modular` with the empty precomputation `()` (call sites pass `&()`).
686///
687/// The `UBig` divisor delegates to the `TypedRepr`-level `DivExact` impls (single-word Hensel,
688/// double-word Hensel, and multi-word Hensel). A primitive `u8..u128`/`usize` divisor that fits in
689/// a `DoubleWord` is divided in place by the same kernels; a wider one falls back to the `UBig`
690/// divisor path.
691impl DivExact<UBig, ()> for UBig {
692    type Output = UBig;
693
694    #[inline]
695    fn div_exact(self, rhs: UBig, _: &()) -> Option<UBig> {
696        self.into_repr().div_exact(rhs.into_repr(), &()).map(UBig)
697    }
698}
699
700impl DivExact<UBig, ()> for &UBig {
701    type Output = UBig;
702
703    #[inline]
704    fn div_exact(self, rhs: UBig, _: &()) -> Option<UBig> {
705        self.clone().div_exact(rhs, &())
706    }
707}
708
709impl DivExactAssign<UBig, ()> for UBig {
710    #[inline]
711    fn div_exact_assign(&mut self, rhs: UBig, _: &()) -> bool {
712        if let TypedReprRef::RefSmall(dword) = rhs.repr() {
713            return self.div_exact_assign_dword(dword);
714        }
715        // A multi-word divisor: back up `self` (an O(len) clone) so a failed division can restore
716        // it, then divide the taken buffer in place.
717        let backup = self.clone();
718        let taken = core::mem::take(self);
719        match taken.into_repr().div_exact(rhs.into_repr(), &()) {
720            Some(q) => {
721                *self = UBig(q);
722                true
723            }
724            None => {
725                *self = backup;
726                false
727            }
728        }
729    }
730}
731
732macro_rules! impl_div_exact_ubig_with_prim {
733    ($($T:ty)*) => {$(
734        impl DivExact<$T, ()> for UBig {
735            type Output = UBig;
736            #[inline]
737            fn div_exact(self, rhs: $T, _: &()) -> Option<UBig> {
738                match DoubleWord::try_from(rhs) {
739                    Ok(dword) => self.into_repr().div_exact(TypedRepr::Small(dword), &()).map(UBig),
740                    Err(_) => DivExact::<UBig, ()>::div_exact(self, UBig::from(rhs), &()),
741                }
742            }
743        }
744        impl DivExactAssign<$T, ()> for UBig {
745            #[inline]
746            fn div_exact_assign(&mut self, rhs: $T, _: &()) -> bool {
747                match DoubleWord::try_from(rhs) {
748                    Ok(dword) => self.div_exact_assign_dword(dword),
749                    Err(_) => {
750                        let (q, r) = (&*self).div_rem(&UBig::from(rhs));
751                        if r.is_zero() {
752                            *self = q;
753                            true
754                        } else {
755                            false
756                        }
757                    }
758                }
759            }
760        }
761    )*};
762}
763impl_div_exact_ubig_with_prim!(u8 u16 u32 u64 u128 usize);
764
765/// `DivExact` / `DivExactAssign` for `IBig`: sign-aware exact division. The magnitudes are divided
766/// by the `UBig` implementations, and the sign of the quotient is the product of the operands'
767/// signs. The primitive divisor impls (unsigned and signed) divide the magnitudes and attach the
768/// sign.
769impl DivExact<IBig, ()> for IBig {
770    type Output = IBig;
771
772    fn div_exact(self, rhs: IBig, _: &()) -> Option<IBig> {
773        let (sign_self, mag_self) = self.into_parts();
774        let (sign_rhs, mag_rhs) = rhs.into_parts();
775        let q_mag = mag_self.div_exact(mag_rhs, &())?;
776        Some(IBig::from_parts(sign_self * sign_rhs, q_mag))
777    }
778}
779
780impl DivExactAssign<IBig, ()> for IBig {
781    fn div_exact_assign(&mut self, rhs: IBig, _: &()) -> bool {
782        if let Some(q) = self.clone().div_exact(rhs, &()) {
783            *self = q;
784            true
785        } else {
786            false
787        }
788    }
789}
790
791impl DivExact<IBig, ()> for &IBig {
792    type Output = IBig;
793
794    #[inline]
795    fn div_exact(self, rhs: IBig, _: &()) -> Option<IBig> {
796        self.clone().div_exact(rhs, &())
797    }
798}
799
800macro_rules! impl_div_exact_ibig_with_prim {
801    ($($T:ty)*) => {$(
802        impl DivExact<$T, ()> for IBig {
803            type Output = IBig;
804            #[inline]
805            fn div_exact(self, rhs: $T, _: &()) -> Option<IBig> {
806                let sign = self.sign();
807                let q_mag = self.unsigned_abs().div_exact(rhs, &())?;
808                Some(IBig::from_parts(sign, q_mag))
809            }
810        }
811        impl DivExactAssign<$T, ()> for IBig {
812            #[inline]
813            fn div_exact_assign(&mut self, rhs: $T, _: &()) -> bool {
814                if let Some(q) = self.clone().div_exact(rhs, &()) {
815                    *self = q;
816                    true
817                } else {
818                    false
819                }
820            }
821        }
822    )*};
823}
824impl_div_exact_ibig_with_prim!(u8 u16 u32 u64 u128 usize);
825
826macro_rules! impl_div_exact_ibig_with_signed_prim {
827    ($($T:ty)*) => {$(
828        impl DivExact<$T, ()> for IBig {
829            type Output = IBig;
830            #[inline]
831            fn div_exact(self, rhs: $T, _: &()) -> Option<IBig> {
832                let sign = if (self.sign() == Sign::Negative) != (rhs < 0) {
833                    Sign::Negative
834                } else {
835                    Sign::Positive
836                };
837                let q_mag = self.unsigned_abs().div_exact(rhs.unsigned_abs(), &())?;
838                Some(IBig::from_parts(sign, q_mag))
839            }
840        }
841        impl DivExactAssign<$T, ()> for IBig {
842            #[inline]
843            fn div_exact_assign(&mut self, rhs: $T, _: &()) -> bool {
844                if let Some(q) = self.clone().div_exact(rhs, &()) {
845                    *self = q;
846                    true
847                } else {
848                    false
849                }
850            }
851        }
852    )*};
853}
854impl_div_exact_ibig_with_signed_prim!(i8 i16 i32 i64 i128 isize);
855
856#[cfg(test)]
857mod tests {
858    use super::*;
859    use crate::{
860        arch::word::Word,
861        primitive::{extend_word, WORD_BITS_USIZE},
862    };
863
864    /// `div_exact_assign` with a single-word divisor must agree with the general division: exact
865    /// (with the quotient in `n`) when `d | n` (here `n = d^i·rest` with `i ≥ 1`), leaving `n`
866    /// unchanged otherwise.
867    #[test]
868    fn test_div_exact_assign_matches_div() {
869        use dashu_base::DivExactAssign;
870
871        for d in [2u16, 3, 5, 7, 10, 12, 16, 25, 255, 1001] {
872            let d = d as Word;
873            for i in 1..10usize {
874                for rest in [1u8, 5, 7, 11] {
875                    let n = UBig::from(d).pow(i) * rest;
876                    let want = &n / UBig::from_word(d);
877                    let mut got = n;
878                    assert!(got.div_exact_assign(extend_word(d), &()), "d={d} i={i} rest={rest}");
879                    assert_eq!(got, want, "d={d} i={i} rest={rest}");
880                }
881            }
882            // a value not divisible by d (and not a multiple of its prime factors) stays unchanged
883            let mut n = UBig::from(d).pow(2) + 1u8;
884            let before = n.clone();
885            assert!(!n.div_exact_assign(extend_word(d), &()), "d={d}");
886            assert_eq!(n, before, "d={d}");
887        }
888    }
889
890    /// `div_exact` must agree with `div_rem` for single-word, double-word, and multi-word divisors
891    /// (odd and even), and return `None` for non-divisible cases.
892    #[test]
893    fn test_div_exact_matches_div() {
894        // single- and double-word divisors
895        for d in [
896            UBig::from(10u8).pow(8), // single word on 64-bit
897            (UBig::ONE << 64) + 3u8, // double word on 64-bit (odd)
898            (UBig::ONE << 70) * 5u8, // double word on 64-bit (even)
899        ] {
900            for i in 1..6usize {
901                let n = d.clone().pow(i) * 7u8;
902                let (q, r) = (&n).div_rem(&d);
903                assert!(r.is_zero(), "d={d:?} i={i}");
904                assert_eq!(n.clone().div_exact(d.clone(), &()), Some(q), "d={d:?} i={i}");
905            }
906            let n = d.clone().pow(2) + 1u8;
907            assert_eq!(n.div_exact(d, &()), None, "d must not divide d^2+1");
908        }
909
910        // multi-word divisors
911        let big = UBig::from(10u8).pow(50);
912        for (a, b) in [
913            (UBig::from(10u8).pow(80) * 7u8, UBig::from(10u8).pow(80)),
914            (big.clone() * UBig::from(13u8), big.clone()),
915            (UBig::from(2u8).pow(300) * 3u8, UBig::from(8u8)),
916        ] {
917            let (q, r) = (&a).div_rem(&b);
918            assert_eq!(a.div_exact(b, &()), if r.is_zero() { Some(q) } else { None });
919        }
920        // not divisible → None (single- and multi-word divisors)
921        assert_eq!(UBig::from(7u8).div_exact(3u8, &()), None);
922        assert_eq!(UBig::from(7u8).div_exact(UBig::from(3u8), &()), None);
923        assert_eq!(UBig::from(7u8).div_exact(big, &()), None);
924    }
925
926    /// The multi-word Hensel kernel must agree with `div_rem` on a sweep of odd divisors (the
927    /// kernel only sees odd divisors; even ones are stripped by [`repr::div_exact_large`]).
928    #[test]
929    fn test_hensel_div_exact_large_matches_div() {
930        for d_bits in [70usize, 100, 150, 300] {
931            let d = (UBig::ONE << d_bits) + 1u8;
932            let dw = d.as_words().to_vec();
933            for i in 1..5usize {
934                let n = d.clone().pow(i) * 12345u16;
935                let want = &n / &d;
936                let mut buf = n.as_words().to_vec();
937                assert!(hensel_div_exact_large(&mut buf, &dw), "d={d_bits} i={i}");
938                assert_eq!(UBig::from_words(&buf), want, "d={d_bits} i={i}");
939            }
940            // not divisible → false
941            let n = d.clone().pow(2) + 2u8;
942            let mut buf = n.as_words().to_vec();
943            assert!(!hensel_div_exact_large(&mut buf, &dw), "d={d_bits}");
944        }
945
946        // even multi-word divisors exercise the 2-split inside `div_exact_large`
947        for d_bits in [70usize, 130, 300] {
948            let odd = (UBig::ONE << d_bits) + 5u8;
949            let d = &odd * UBig::from(16u8);
950            let n = d.clone().pow(3) * 77u8;
951            let (q, r) = (&n).div_rem(&d);
952            assert!(r.is_zero());
953            assert_eq!(n.clone().div_exact(d.clone(), &()), Some(q));
954            // a value whose odd part divides but whose 2-valuation is too low
955            assert_eq!((odd * 7u8).div_exact(d, &()), None);
956        }
957    }
958
959    /// `div_exact_assign` with a double-word divisor. The divisors are built relative to the word
960    /// size so they need two words on every platform: `Word::MAX²` is just below
961    /// `DoubleWord::MAX`, so it (and its neighbours) always span two words.
962    #[test]
963    fn test_div_exact_assign_dword() {
964        use dashu_base::DivExactAssign;
965
966        let base = extend_word(Word::MAX) * extend_word(Word::MAX); // Word::MAX² (odd)
967        for d in [
968            base + 2,                                       // odd double word
969            base + 3,                                       // even double word
970            (1 as DoubleWord) << (2 * WORD_BITS_USIZE - 1), // power of two
971        ] {
972            let d_ubig = UBig::from_dword(d);
973            for i in 1..6usize {
974                let n = d_ubig.clone().pow(i) * 7u8;
975                let want = &n / &d_ubig;
976                let mut got = n;
977                assert!(got.div_exact_assign(d, &()), "d={d:?} i={i}");
978                assert_eq!(got, want, "d={d:?} i={i}");
979            }
980            let mut n = d_ubig.clone().pow(2) + 1u8;
981            let before = n.clone();
982            assert!(!n.div_exact_assign(d, &()), "d={d:?}");
983            assert_eq!(n, before, "d={d:?}");
984        }
985    }
986
987    /// The `DivExact`/`DivExactAssign` trait impls: primitive divisors (including one wider than
988    /// `Word`, which falls back to the `UBig` divisor path) and the in-place assign form.
989    #[test]
990    fn test_div_exact_trait_impls() {
991        use dashu_base::{DivExact, DivExactAssign};
992
993        // UBig ÷ UBig
994        let a = UBig::from(10u8).pow(8) * 7u8;
995        assert_eq!(a.clone().div_exact(UBig::from(10u8).pow(8), &()), Some(UBig::from(7u8)));
996        assert_eq!(a.div_exact(UBig::from(3u8), &()), None);
997
998        // UBig ÷ primitives — any width, including one that overflows Word (u128 on 64-bit Word)
999        assert_eq!(UBig::from(10u8).pow(8).div_exact(10u8, &()), Some(UBig::from(10u8).pow(7)));
1000        assert_eq!(UBig::from(10u8).pow(8).div_exact(10u32, &()), Some(UBig::from(10u8).pow(7)));
1001        assert_eq!(UBig::from(10u8).pow(8).div_exact(10u128, &()), Some(UBig::from(10u8).pow(7)));
1002        let wide = 1u128 << 100; // > Word::MAX on any current platform
1003        assert_eq!(UBig::from(10u8).pow(8).div_exact(wide, &()), None);
1004        assert_eq!(UBig::from(wide).div_exact(1u128, &()), Some(UBig::from(wide)));
1005
1006        // DivExactAssign with a primitive (in place)
1007        let mut b = UBig::from(10u8).pow(8) * 7u8;
1008        assert!(b.div_exact_assign(10u8, &()));
1009        assert_eq!(b, UBig::from(10u8).pow(7) * 7u8);
1010        assert!(!b.div_exact_assign(3u8, &())); // not divisible → unchanged
1011        assert_eq!(b, UBig::from(10u8).pow(7) * 7u8);
1012
1013        // DivExactAssign with a multi-word divisor, unchanged on failure
1014        let d = UBig::from(10u8).pow(50);
1015        let mut c = d.clone().pow(2) * 7u8;
1016        assert!(c.div_exact_assign(d.clone(), &()));
1017        assert_eq!(c, &d * 7u8);
1018        let mut c = d.clone().pow(2) * 7u8;
1019        let before = c.clone();
1020        assert!(!c.div_exact_assign(d.clone() + 1u8, &()));
1021        assert_eq!(c, before);
1022
1023        // reference receiver keeps the dividend borrowable
1024        let ref_a = UBig::from(10u8).pow(8) * 7u8;
1025        assert_eq!((&ref_a).div_exact(UBig::from(10u8).pow(8), &()), Some(UBig::from(7u8)));
1026        assert_eq!((&ref_a).div_exact(UBig::from(3u8), &()), None);
1027        assert_eq!(ref_a, UBig::from(10u8).pow(8) * 7u8); // unchanged
1028    }
1029
1030    /// The `DivExact`/`DivExactAssign` trait impls for `IBig`: sign-aware exact division, primitive
1031    /// divisors (unsigned and signed), and the in-place form.
1032    #[test]
1033    fn test_div_exact_ibig() {
1034        use dashu_base::{DivExact, DivExactAssign};
1035
1036        // IBig ÷ IBig
1037        let a = IBig::from(10u8).pow(8) * 7u8;
1038        assert_eq!(a.clone().div_exact(IBig::from(10u8).pow(8), &()), Some(IBig::from(7u8)));
1039        assert_eq!(a.div_exact(IBig::from(3u8), &()), None);
1040        // signs: quotient sign is the product of the operands' signs
1041        assert_eq!(IBig::from(-14i32).div_exact(IBig::from(7i32), &()), Some(IBig::from(-2i32)));
1042        assert_eq!(IBig::from(14i32).div_exact(IBig::from(-7i32), &()), Some(IBig::from(-2i32)));
1043        assert_eq!(IBig::from(-14i32).div_exact(IBig::from(-7i32), &()), Some(IBig::from(2i32)));
1044
1045        // reference receiver keeps the dividend borrowable
1046        let ref_a = IBig::from(10u8).pow(8) * 7u8;
1047        assert_eq!((&ref_a).div_exact(IBig::from(10u8).pow(8), &()), Some(IBig::from(7u8)));
1048        assert_eq!((&ref_a).div_exact(IBig::from(3u8), &()), None);
1049        assert_eq!(ref_a, IBig::from(10u8).pow(8) * 7u8); // unchanged
1050
1051        // primitive divisors
1052        assert_eq!(IBig::from(10u8).pow(8).div_exact(10u8, &()), Some(IBig::from(10u8).pow(7)));
1053        assert_eq!(IBig::from(-20i32).div_exact(5i32, &()), Some(IBig::from(-4i32)));
1054        assert_eq!(IBig::from(20i32).div_exact(-5i32, &()), Some(IBig::from(-4i32)));
1055        assert_eq!(IBig::from(20i32).div_exact(7i32, &()), None);
1056
1057        // DivExactAssign
1058        let mut b = IBig::from(10u8).pow(8) * 7u8;
1059        assert!(b.div_exact_assign(IBig::from(10u8).pow(8), &()));
1060        assert_eq!(b, IBig::from(7u8));
1061        assert!(!b.div_exact_assign(3u8, &())); // unchanged on failure
1062        assert_eq!(b, IBig::from(7u8));
1063        assert!(b.div_exact_assign(-7i32, &()));
1064        assert_eq!(b, IBig::from(-1i32));
1065    }
1066
1067    /// `is_multiple_of` must agree with the remainder check for single-word, double-word, and
1068    /// multi-word divisors (odd, even, and power-of-two).
1069    #[test]
1070    fn test_is_multiple_of_matches_rem() {
1071        // single-word divisors (via the read-only Hensel test)
1072        for d in [2u16, 3, 5, 7, 10, 12, 16, 25, 255] {
1073            let d = d as Word;
1074            for i in 1..10usize {
1075                for rest in [1u8, 5, 7, 11] {
1076                    let n = UBig::from(d).pow(i) * rest;
1077                    let want = (&n % UBig::from_word(d)).is_zero();
1078                    assert_eq!(
1079                        n.is_multiple_of(&UBig::from_word(d)),
1080                        want,
1081                        "d={d} i={i} rest={rest}"
1082                    );
1083                }
1084            }
1085        }
1086
1087        // double-word divisors
1088        for d in [
1089            (UBig::ONE << 64) + 3u8,
1090            (UBig::ONE << 70) * 5u8,
1091            UBig::ONE << 100,
1092        ] {
1093            for i in 1..8usize {
1094                let n = d.clone().pow(i) * 7u8;
1095                let want = (&n % &d).is_zero();
1096                assert_eq!(n.is_multiple_of(&d), want, "d={d:?} i={i}");
1097            }
1098            let n = d.clone().pow(2) + 1u8;
1099            assert!(!n.is_multiple_of(&d));
1100        }
1101
1102        // multi-word divisors
1103        let d = (UBig::ONE << 200) + 1u8;
1104        for i in 1..6usize {
1105            let n = d.clone().pow(i) * 11u8;
1106            let want = (&n % &d).is_zero();
1107            assert_eq!(n.is_multiple_of(&d), want, "i={i}");
1108        }
1109        assert!(!(d.clone().pow(2) + 2u8).is_multiple_of(&d));
1110    }
1111
1112    /// The `const` divisibility test agrees with the remainder for both word and dword divisors.
1113    #[test]
1114    fn test_is_multiple_of_const_matches_rem() {
1115        for (n, d) in [
1116            (UBig::from(24u8), 6u8),
1117            (UBig::from(24u8), 7u8),
1118            (UBig::from(10u8).pow(8), 10u8),
1119            (UBig::from(10u8).pow(8), 3u8),
1120        ] {
1121            assert_eq!(
1122                n.is_multiple_of_const(d as DoubleWord),
1123                (&n % UBig::from_word(d as Word)).is_zero()
1124            );
1125        }
1126    }
1127}