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}