Skip to content

Commit 7310997

Browse files
authored
Merge pull request #350 from cuviper/bz-long-division
Improve the implementation of Burnikel-Ziegler division
2 parents 312cb3b + 90bd785 commit 7310997

2 files changed

Lines changed: 73 additions & 53 deletions

File tree

src/biguint/division.rs

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::addition::__add2;
22
use super::shift::biguint_shl;
3-
use super::{cmp_slice, ilog2, BigUint};
3+
use super::{cmp_slice, BigUint};
44

55
use crate::big_digit::{self, BigDigit, BigDigits, DoubleBigDigit, BITS};
66
use crate::UsizePromotion;
@@ -193,29 +193,36 @@ fn div_rem_cow(u: Cow<'_, BigUint>, d: Cow<'_, BigUint>) -> (BigUint, BigUint) {
193193
Greater => {} // Do nothing
194194
}
195195

196+
// Larger operands use the algorithm from Burnikel and Ziegler,
197+
// which has its own similar shift-normalization strategy.
198+
if (u.data.len() > BURNIKEL_ZIEGLER_THRESHOLD * 2)
199+
&& (d.data.len() > BURNIKEL_ZIEGLER_THRESHOLD)
200+
{
201+
return div_rem_burnikel_ziegler(u, d);
202+
}
203+
196204
// This algorithm is from Knuth, TAOCP vol 2 section 4.3, algorithm D:
197205
//
198206
// First, normalize the arguments so the highest bit in the highest digit of the divisor is
199207
// set: the main loop uses the highest digit of the divisor for generating guesses, so we
200208
// want it to be the largest number we can efficiently divide by.
201209
//
202210
let shift = d.data.last().unwrap().leading_zeros();
203-
204211
if shift == 0 {
205212
// no need to clone d
206-
div_rem_core(u, &d)
213+
div_rem_core(u.into_owned(), &d)
207214
} else {
208215
let u = biguint_shl(u, shift);
209216
let d = biguint_shl(d, shift);
210-
let (q, r) = div_rem_core(Cow::Owned(u), &d);
217+
let (q, r) = div_rem_core(u, &d);
211218
// renormalize the remainder
212219
(q, r >> shift)
213220
}
214221
}
215222

216223
const BURNIKEL_ZIEGLER_THRESHOLD: usize = 64;
217224

218-
/// This algorithm is from Burnikel and Ziegler, "Fast Recursive Division", Algorithm 1.
225+
/// This algorithm is from Burnikel and Ziegler, "Fast Recursive Division".
219226
/// It is a recursive algorithm that divides the dividend and divisor into blocks of digits
220227
/// and uses a divide-and-conquer approach to find the quotient.
221228
///
@@ -224,7 +231,7 @@ const BURNIKEL_ZIEGLER_THRESHOLD: usize = 64;
224231
/// Time complexity of this algorithm is the same as the algorithm used for the multiplication.
225232
///
226233
/// link: https://pure.mpg.de/rest/items/item_1819444_4/component/file_2599480/content
227-
fn div_rem_burnikel_ziegler(u: &BigUint, d: &BigUint) -> (BigUint, BigUint) {
234+
fn div_rem_burnikel_ziegler(a: Cow<'_, BigUint>, b: Cow<'_, BigUint>) -> (BigUint, BigUint) {
228235
fn divide_biguint(mut b: BigUint, level: usize) -> (BigUint, BigUint) {
229236
if b.data.len() <= level {
230237
return (BigUint::ZERO, b);
@@ -255,28 +262,13 @@ fn div_rem_burnikel_ziegler(u: &BigUint, d: &BigUint) -> (BigUint, BigUint) {
255262
b: BigUint,
256263
level: usize,
257264
) -> (BigUint, BigUint) {
258-
// A precondition of this function is that q fits into a single digit.
265+
// The quotient must fit into a single digit, i.e. A < βⁿB.
259266
debug_assert!(ah < b);
267+
// The divisor must also have its MSB set, i.e. βⁿ/2 ≤ B < βⁿ.
268+
debug_assert_eq!(normalizing_shift_amount(&b, level), 0);
260269
if level <= BURNIKEL_ZIEGLER_THRESHOLD {
261270
return div_rem(concat_biguint(&ah, al, level), b);
262271
}
263-
let shift = normalizing_shift_amount(&b, level);
264-
if shift != 0 {
265-
let b = b << shift;
266-
let (ah, al) = divide_biguint(concat_biguint(&ah, al, level) << shift, level);
267-
let (q, r) = div_two_digit_by_one_normalized(ah, al, b, level);
268-
(q, r >> shift)
269-
} else {
270-
div_two_digit_by_one_normalized(ah, al, b, level)
271-
}
272-
}
273-
274-
fn div_two_digit_by_one_normalized(
275-
ah: BigUint,
276-
al: BigUint,
277-
b: BigUint,
278-
level: usize,
279-
) -> (BigUint, BigUint) {
280272
let level = level / 2;
281273
let (a1, a2) = divide_biguint(ah, level);
282274
let (a3, a4) = divide_biguint(al, level);
@@ -325,28 +317,59 @@ fn div_rem_burnikel_ziegler(u: &BigUint, d: &BigUint) -> (BigUint, BigUint) {
325317
(q, r - d)
326318
}
327319

328-
let mut level = 1 << ilog2(u.data.len());
329-
if d.data.len() > level {
330-
level *= 2;
320+
fn div_blocks(a: &BigUint, b: &BigUint, level: usize) -> (BigUint, BigUint) {
321+
let mut a_blocks = a.data.chunks(level);
322+
323+
// If the most-significant block is already less than b,
324+
// then it serves as the initial remainder.
325+
let mut r = BigUint::ZERO;
326+
if let Some(ah) = a_blocks.clone().next_back() {
327+
if cmp_slice(ah, &b.data) == Less {
328+
_ = a_blocks.next_back();
329+
// NB: assuming a is normalized, then so is r
330+
r = BigUint {
331+
data: BigDigits::from_slice(ah),
332+
};
333+
}
334+
}
335+
336+
let mut q_data = vec![0; a_blocks.len() * level];
337+
let q_blocks = q_data.chunks_mut(level);
338+
339+
// Iterate block-by-block like long division
340+
for (q, ai) in q_blocks.zip(a_blocks).rev() {
341+
let mut ai = BigUint {
342+
data: BigDigits::from_slice(ai),
343+
};
344+
ai.data.normalize();
345+
let (qi, ri) = div_two_digit_by_one(r, ai, b.clone(), level);
346+
q[..qi.data.len()].copy_from_slice(&qi.data);
347+
r = ri;
348+
}
349+
350+
let mut q = BigUint {
351+
data: BigDigits::from_vec(q_data),
352+
};
353+
q.data.normalize();
354+
(q, r)
331355
}
332-
let (u1, u2) = divide_biguint(u.clone(), level);
333-
if &u1 >= d {
334-
div_two_digit_by_one(BigUint::ZERO, u.clone(), d.clone(), level * 2)
356+
357+
// The block size is the minimum 2ᵏ that fits the divisor.
358+
let level = b.data.len().next_power_of_two();
359+
let shift = normalizing_shift_amount(&b, level);
360+
if shift == 0 {
361+
div_blocks(&a, &b, level)
335362
} else {
336-
div_two_digit_by_one(u1, u2, d.clone(), level)
363+
let a = biguint_shl(a, shift);
364+
let b = biguint_shl(b, shift);
365+
let (q, r) = div_blocks(&a, &b, level);
366+
(q, r >> shift)
337367
}
338368
}
339369

340370
/// An implementation of the base division algorithm.
341371
/// Knuth, TAOCP vol 2 section 4.3.1, algorithm D, with an improvement from exercises 19-21.
342-
fn div_rem_core(a: Cow<'_, BigUint>, b: &BigUint) -> (BigUint, BigUint) {
343-
if (a.data.len() > BURNIKEL_ZIEGLER_THRESHOLD * 2)
344-
&& (b.data.len() > BURNIKEL_ZIEGLER_THRESHOLD)
345-
{
346-
return div_rem_burnikel_ziegler(&a, b);
347-
}
348-
349-
let mut a = a.into_owned();
372+
fn div_rem_core(mut a: BigUint, b: &BigUint) -> (BigUint, BigUint) {
350373
let b = &*b.data;
351374
debug_assert!(a.data.len() >= b.len() && b.len() > 1);
352375
debug_assert!(b.last().unwrap().leading_zeros() == 0);

tests/biguint.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -924,18 +924,16 @@ fn test_div_rem_big_multiple() {
924924

925925
#[test]
926926
fn test_div_rem_burnikel_ziegler_u1_equals_d() {
927-
// Construct u and d such that the B-Z entry point sees u1 == d.
927+
// Construct u and d such that div_blocks' most-significant block equals d.
928928
//
929929
// d has 65 u64-digits (130 u32-digits) with MSB set, ensuring:
930930
// - d.len() > 64 (enters B-Z)
931-
// - no normalization shift (MSB already set)
931+
// - level = next_power_of_two(65) = 128
932932
//
933-
// u = d << 8192 + 42, so u's high part (split at `level` digits) is exactly d.
934-
// For u64: u.len()=193, level=128, split gives u1=d
935-
// For u32: u.len()=386, level=256, split gives u1=d
936-
//
937-
// The entry point originally had `if &u1 > d` (should be >=), so the else branch was
938-
// taken with u1==d, violating div_two_digit_by_one's precondition ah < b.
933+
// The normalization shift pads d to fill the full 128 digits. Then u = d << 8192 + 42 is also
934+
// shifted, so the most-significant block of u (chunked at `level` digits) equals d exactly.
935+
// So div_blocks must not peel it off as the initial remainder (since it's not strictly less
936+
// than d), instead starting with r = 0.
939937
let d = (BigUint::one() << 4159usize) + BigUint::one();
940938
let remainder = BigUint::from(42u32);
941939
let u = (&d << 8192usize) + &remainder;
@@ -951,12 +949,11 @@ fn test_div_rem_burnikel_ziegler_a1_equals_b1() {
951949
// This triggered a bug for the missing a1 >= b1 guard in div_three_halves_by_two
952950
// (Algorithm 2, step 3b of the Burnikel-Ziegler paper).
953951
//
954-
// d has 65 u64-digits (130 u32-digits). In div_two_digit_by_one, the normalization
955-
// shift pads d to fill `level` digits. After div_two_digit_by_one_normalized splits
956-
// both ah and the shifted d at the half-level, the second div_three_halves_by_two call
957-
// receives a remainder whose upper half equals b1 (the upper half of the shifted d).
958-
// It then called div_two_digit_by_one(a1, a2, b1, level) with a1 == b1, violating the
959-
// a1 < b1 precondition.
952+
// d has 65 u64-digits (130 u32-digits). The top-level normalization shift pads d to fill
953+
// `level` digits, and div_two_digit_by_one splits both ah and the shifted d at the half-level.
954+
// Then the second div_three_halves_by_two call receives a remainder whose upper half equals b1
955+
// (the upper half of the shifted d). It then called div_two_digit_by_one(a1, a2, b1, level)
956+
// with a1 == b1, violating the a1 < b1 precondition.
960957
//
961958
// The quotient ((1 << 4096) - 1) is chosen so the remainder is d - 1, which shares
962959
// the same upper half as d after normalization.

0 commit comments

Comments
 (0)