Skip to main content

dashu_int/
remove.rs

1use crate::{
2    div_exact::{hensel_div_odd_in_place, hensel_is_multiple_of},
3    math::inv_mod_pow2,
4    primitive::{extend_word, shrink_dword, WORD_BITS, WORD_BITS_USIZE},
5    repr::{Repr, TypedReprRef},
6    shift,
7    ubig::UBig,
8    Word,
9};
10use alloc::vec;
11use dashu_base::{DivRem, PowerOfTwo};
12
13impl UBig {
14    /// Divide out all multiples of the factor from the integer,
15    /// returns the exponent of the removed factor.
16    ///
17    /// For self = 0 or factor = 0 or 1, this method returns None.
18    ///
19    /// # Examples
20    ///
21    /// ```
22    /// use dashu_int::UBig;
23    ///
24    /// let mut a = UBig::from(8u32) * 3u32;
25    /// assert_eq!(a.remove(&UBig::from(2u32)), Some(3));
26    /// assert_eq!(a, UBig::from(3u32));
27    /// ```
28    pub fn remove(&mut self, factor: &UBig) -> Option<usize> {
29        // A factor that fits in a single word is handled by the faster `remove_word`.
30        if let TypedReprRef::RefSmall(dword) = factor.repr() {
31            if let Some(word) = shrink_dword(dword) {
32                return self.remove_word(word);
33            }
34        }
35
36        if self.is_zero() || factor.is_zero() || factor.is_one() {
37            return None;
38        }
39
40        // shortcut for power of 2
41        if factor.is_power_of_two() {
42            let bits = factor.trailing_zeros().unwrap();
43            let exp = self.trailing_zeros().unwrap() / bits;
44            *self >>= exp * bits;
45            return Some(exp);
46        }
47
48        let (mut q, r) = (&*self).div_rem(factor);
49        if !r.is_zero() {
50            return Some(0);
51        }
52
53        // first stage, division with exponentially growing factors
54        let mut exp = 1;
55        let mut pows = vec![factor.sqr()];
56        loop {
57            let last = pows.last().unwrap();
58            let (new_q, r) = (&q).div_rem(last);
59            if !r.is_zero() {
60                break;
61            }
62
63            exp += 1 << pows.len();
64            q = new_q;
65            let next_sq = last.sqr();
66            pows.push(next_sq);
67        }
68
69        // second stage, division from highest power to the lowest
70        while let Some(last) = pows.pop() {
71            let (new_q, r) = (&q).div_rem(last);
72            if r.is_zero() {
73                exp += 1 << (pows.len() + 1);
74                q = new_q;
75            }
76        }
77
78        // last division
79        let (new_q, r) = (&q).div_rem(factor);
80        if r.is_zero() {
81            exp += 1;
82            q = new_q;
83        }
84
85        *self = q;
86        Some(exp)
87    }
88
89    /// Divide out all multiples of a single-word factor from the integer,
90    /// returns the exponent of the removed factor.
91    ///
92    /// The single-word specialization of [`remove`](Self::remove): the factor's power-of-two part is
93    /// stripped by the 2-valuation, and the odd part is divided out by **Hensel (2-adic) exact
94    /// division** — the modular inverse of the factor is precomputed by Newton iteration, and each
95    /// quotient limb is computed as `(word − carry) · d^{-1} mod 2^WORD_BITS`, i.e. only multiplies
96    /// and subtracts with no division or normalization. Powers `d², d⁴, …` (which exceed a single
97    /// word) use the general division, mirroring the binary-splitting in [`remove`](Self::remove).
98    ///
99    /// For self = 0 or factor = 0 or 1, this method returns None.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use dashu_int::UBig;
105    ///
106    /// let mut a = UBig::from(10u32).pow(8) * 7u32;
107    /// assert_eq!(a.remove_word(10), Some(8));
108    /// assert_eq!(a, UBig::from(7u32));
109    /// ```
110    pub fn remove_word(&mut self, factor: Word) -> Option<usize> {
111        if self.is_zero() || factor == 0 || factor == 1 {
112            return None;
113        }
114
115        // factor = 2^s · d_odd: the power-of-two part and the odd part are removed separately.
116        let trailing = factor.trailing_zeros();
117        let s = trailing as usize;
118        let d_odd = factor >> trailing;
119
120        if d_odd == 1 {
121            // A pure power of two: the exponent is bounded by the 2-valuation of self.
122            let exp = self.trailing_zeros().unwrap() / s;
123            *self >>= exp * s;
124            return Some(exp);
125        }
126
127        if s == 0 {
128            // An odd factor: no power-of-two part to reconcile.
129            return Some(remove_odd_powers(self, d_odd, usize::MAX));
130        }
131
132        // A mixed factor (e.g. 10 = 2·5): strip the trailing 2s once, divide out the odd part (each
133        // full `factor`-power consumes `s` of them, so the odd-part count is capped by `tz/s`), then
134        // reassemble the cofactor `self / factor^exp = (odd part) << (tz − s·exp)` (the shift amount
135        // is non-negative because `exp ≤ cap = tz/s`). `self` is taken and its own buffer is shifted
136        // right by `tz` in place (the low `tz` bits are zero by construction) — no allocation for the
137        // working value; `*self` is rebuilt from the result.
138        let tz = self.trailing_zeros().unwrap();
139        let cap = tz / s;
140        let mut odd_buf = core::mem::take(self).0.into_buffer();
141        odd_buf.erase_front(tz / WORD_BITS_USIZE);
142        if tz % WORD_BITS_USIZE != 0 {
143            shift::shr_in_place(&mut odd_buf, (tz % WORD_BITS_USIZE) as u32);
144        }
145        odd_buf.pop_zeros();
146        let mut odd = UBig(Repr::from_buffer(odd_buf));
147        let exp = remove_odd_powers(&mut odd, d_odd, cap);
148        *self = odd << (tz - s * exp);
149        Some(exp)
150    }
151}
152
153/// Remove powers of the odd single-word `d` from `n` (in place), at most `cap` of them, and return
154/// the number removed. `n` is left unchanged when `d` does not divide it.
155///
156/// The first division by `d` uses the Hensel (2-adic) exact division
157/// ([`crate::div_exact::hensel_div_odd_in_place`]), which computes the quotient with only multiplies
158/// and subtracts; the powers `d², d⁴, …` that follow exceed a single word and use the general
159/// division, mirroring the binary-splitting of [`UBig::remove`].
160fn remove_odd_powers(n: &mut UBig, d: Word, cap: usize) -> usize {
161    if cap == 0 {
162        return 0;
163    }
164    let di = inv_mod_pow2(extend_word(d), WORD_BITS) as Word;
165
166    // A read-only divisibility probe first leaves `n` untouched when `d` does not divide it, so the
167    // in-place Hensel division that follows is guaranteed to succeed and can consume `n`'s own
168    // buffer — no scratch allocation.
169    if !hensel_is_multiple_of(n.as_words(), d, di) {
170        return 0;
171    }
172    let mut q = core::mem::take(n).0.into_buffer();
173    let exact = hensel_div_odd_in_place(&mut q, d, di);
174    debug_assert!(exact, "the probe passed, so the division is exact");
175    *n = UBig(Repr::from_buffer(q));
176    let mut exp = 1;
177
178    // Grow the powers d², d⁴, … while each divides the current quotient exactly (and stays within
179    // `cap`), then refine downward with the collected powers, and finally divide out one last single
180    // `d` (the count may be odd, leaving a leftover below d²).
181    let mut power = UBig::from_word(d).sqr();
182    let mut power_exp = 2usize;
183    let mut pows = vec![(power.clone(), power_exp)];
184    while exp + power_exp <= cap && *n >= power {
185        let (new_q, r) = (&*n).div_rem(&power);
186        if r.is_zero() {
187            *n = new_q;
188            exp += power_exp;
189            power = power.sqr();
190            power_exp <<= 1;
191            pows.push((power.clone(), power_exp));
192        } else {
193            break;
194        }
195    }
196    while let Some((p, e)) = pows.pop() {
197        if exp + e > cap {
198            continue;
199        }
200        let (new_q, r) = (&*n).div_rem(&p);
201        if r.is_zero() {
202            *n = new_q;
203            exp += e;
204        }
205    }
206    if exp < cap {
207        let (new_q, r) = (&*n).div_rem(&UBig::from_word(d));
208        if r.is_zero() {
209            *n = new_q;
210            exp += 1;
211        }
212    }
213    exp
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate::UBig;
220
221    /// The Hensel kernel must agree with the general division on a sweep of small values — both the
222    /// exactness test (remainder == 0) and the quotient itself.
223    #[test]
224    fn test_hensel_div_matches_div_rem() {
225        for d in [3u16, 5, 7, 9, 15, 21, 255, 1001] {
226            let d = d as Word;
227            let d = d as Word;
228            for lo in [0u8, 1, 2, 5, 200, 255] {
229                let lo = lo as Word;
230                for hi in [0u8, 1, 2, 7, 199] {
231                    let hi = hi as Word;
232                    let u = UBig::from_words(&[lo, hi]);
233                    if u.is_zero() {
234                        continue;
235                    }
236                    let mut q: alloc::vec::Vec<Word> = u.as_words().to_vec();
237                    let di = inv_mod_pow2(extend_word(d), WORD_BITS) as Word;
238                    let exact = hensel_div_odd_in_place(&mut q, d, di);
239                    let (qr, r) = (&u).div_rem(&UBig::from_word(d));
240                    assert_eq!(exact, r.is_zero(), "d={d} u={u:?}");
241                    if r.is_zero() {
242                        assert_eq!(UBig::from_words(&q), qr, "quotient d={d} u={u:?}");
243                    }
244                }
245            }
246        }
247    }
248
249    /// The Newton inverse must satisfy `d · d^{-1} ≡ 1 (mod 2^WORD_BITS)` for odd `d`.
250    #[test]
251    fn test_inv_mod_pow2() {
252        for d in [3u8, 5, 7, 9, 11, 15, 31, 127, 255] {
253            let d = d as Word;
254            let inv = inv_mod_pow2(extend_word(d), WORD_BITS) as Word;
255            assert_eq!(d.wrapping_mul(inv), 1, "d·d^-1 ≡ 1 for d={d}",);
256        }
257    }
258
259    /// `remove_word` must agree with `remove` for every single-word factor, on both odd and even
260    /// factors and with extra 2s mixed in.
261    #[test]
262    fn test_remove_word_matches_remove() {
263        for d in [2u8, 3, 5, 7, 10, 12, 16, 25] {
264            let d = d as Word;
265            for i in 0..10usize {
266                for rest in [1u8, 5, 7, 11] {
267                    let base = UBig::from(d).pow(i) * rest;
268                    for extra_twos in 0..3usize {
269                        let n = base.clone() << extra_twos;
270                        let mut a = n.clone();
271                        let exp_a = a.remove_word(d);
272                        let mut b = n.clone();
273                        let exp_b = b.remove(&UBig::from(d));
274                        assert_eq!(exp_a, exp_b, "d={d} i={i} rest={rest} twos={extra_twos}");
275                        assert_eq!(a, b, "d={d} i={i} rest={rest} twos={extra_twos}");
276                    }
277                }
278            }
279        }
280    }
281
282    /// A not-divisible factor must leave the value unchanged (`remove_word` computes the quotient in
283    /// a scratch and only commits it on an exact division).
284    #[test]
285    fn test_remove_word_noop_when_not_divisible() {
286        for n in [3u32, 7, 9, 11, 13, 25, 100, 1000] {
287            for d in [2u8, 3, 5, 7, 10] {
288                let d = d as Word;
289                let original = UBig::from(n);
290                let mut a = original.clone();
291                if a.remove_word(d) == Some(0) {
292                    assert_eq!(a, original, "not-divisible must leave n unchanged: d={d} n={n}");
293                }
294            }
295        }
296    }
297
298    /// Multi-word operands, including a pure power of two, a mixed factor, and a large odd factor.
299    #[test]
300    fn test_remove_word_large() {
301        let n = UBig::from(10u8).pow(200);
302        let mut a = n.clone();
303        assert_eq!(a.remove_word(10), Some(200));
304        assert_eq!(a, UBig::ONE);
305
306        // 5^200 · 2^100 · 7: the odd factor's 2-valuation is irrelevant to it.
307        let mut b = UBig::from(5u8).pow(200) * (UBig::ONE << 100) * 7u8;
308        assert_eq!(b.remove_word(5), Some(200));
309        assert_eq!(b, (UBig::ONE << 100) * 7u8);
310
311        // A pure power of two.
312        let mut c = (UBig::ONE << 1000) * 3u8;
313        assert_eq!(c.remove_word(4), Some(500));
314        assert_eq!(c, UBig::from(3u8));
315    }
316}