use super::uint::{normalize, BigUint};
pub fn divrem(u: &BigUint, v: &BigUint) -> (BigUint, BigUint) {
match checked_divrem(u, v) {
Some(qr) => qr,
None => panic!("BigUint: division by zero"),
}
}
pub fn checked_divrem(u: &BigUint, v: &BigUint) -> Option<(BigUint, BigUint)> {
if v.limbs.is_empty() {
return None;
}
if u.cmp(v) == core::cmp::Ordering::Less {
return Some((BigUint::zero(), u.clone()));
}
if v.limbs.len() == 1 {
let d = v.limbs[0];
return Some(div_single_limb(u, d));
}
if v.limbs.len() >= NEWTON_DIV_THRESHOLD {
return Some(div_burnikel_ziegler(u, v));
}
Some(div_knuth_d(u, v))
}
fn div_single_limb(u: &BigUint, d: u64) -> (BigUint, BigUint) {
debug_assert!(d != 0, "single-limb divisor zero handled at caller");
let mut q: Vec<u64> = vec![0u64; u.limbs.len()];
let mut r: u64 = 0;
for i in (0..u.limbs.len()).rev() {
let dividend: u128 = ((r as u128) << 64) | (u.limbs[i] as u128);
q[i] = (dividend / (d as u128)) as u64;
r = (dividend % (d as u128)) as u64;
}
normalize(&mut q);
let rem = if r == 0 {
BigUint::zero()
} else {
BigUint { limbs: vec![r] }
};
(BigUint { limbs: q }, rem)
}
fn div_knuth_d(u: &BigUint, v: &BigUint) -> (BigUint, BigUint) {
debug_assert!(v.limbs.len() >= 2);
debug_assert!(u.cmp(v) != core::cmp::Ordering::Less);
let shift = v.limbs[v.limbs.len() - 1].leading_zeros();
let v_norm = v.shl_bits(shift as u64);
let mut u_norm: Vec<u64> = if shift == 0 {
let mut t = u.limbs.clone();
t.push(0);
t
} else {
let mut t: Vec<u64> = Vec::with_capacity(u.limbs.len() + 1);
let mut carry: u64 = 0;
for &limb in &u.limbs {
t.push((limb << shift) | carry);
carry = limb >> (64 - shift);
}
t.push(carry);
t
};
let v_norm_limbs = v_norm.limbs.as_slice();
debug_assert!(v_norm_limbs[v_norm_limbs.len() - 1] >= 1u64 << 63);
debug_assert_eq!(
v_norm_limbs.len(),
v.limbs.len(),
"shift cannot add a limb to v because top limb was nonzero"
);
let n = v_norm_limbs.len();
debug_assert!(u_norm.len() > n);
let m = u_norm.len() - n - 1;
let mut q: Vec<u64> = vec![0u64; m + 1];
let b_u128: u128 = 1u128 << 64;
for j in (0..=m).rev() {
let u_hi = u_norm[j + n] as u128;
let u_mid = u_norm[j + n - 1] as u128;
let dividend: u128 = (u_hi << 64) | u_mid;
let v_top = v_norm_limbs[n - 1] as u128;
let mut qhat: u128 = dividend / v_top;
let mut rhat: u128 = dividend % v_top;
if qhat >= b_u128 {
qhat = b_u128 - 1;
rhat = dividend - qhat * v_top;
}
let v_sub1 = v_norm_limbs[n - 2] as u128;
let u_sub2 = u_norm[j + n - 2] as u128;
while qhat * v_sub1 > (rhat << 64) | u_sub2 {
qhat -= 1;
rhat += v_top;
if rhat >= b_u128 {
break;
}
}
let qhat_u64: u64 = qhat as u64;
let mut borrow: i128 = 0;
let mut carry_mul: u128 = 0;
for i in 0..n {
let prod: u128 = (qhat_u64 as u128) * (v_norm_limbs[i] as u128) + carry_mul;
let prod_lo: u64 = prod as u64;
carry_mul = prod >> 64;
let cur = u_norm[j + i] as i128;
let diff = cur - (prod_lo as i128) - borrow;
if diff < 0 {
u_norm[j + i] = (diff + (1i128 << 64)) as u64;
borrow = 1;
} else {
u_norm[j + i] = diff as u64;
borrow = 0;
}
}
let cur = u_norm[j + n] as i128;
let diff = cur - (carry_mul as i128) - borrow;
let needs_addback: bool;
if diff < 0 {
u_norm[j + n] = (diff + (1i128 << 64)) as u64;
needs_addback = true;
} else {
u_norm[j + n] = diff as u64;
needs_addback = false;
}
if needs_addback {
q[j] = qhat_u64.wrapping_sub(1);
let mut carry: u64 = 0;
for i in 0..n {
let (s1, c1) = u_norm[j + i].overflowing_add(v_norm_limbs[i]);
let (s2, c2) = s1.overflowing_add(carry);
u_norm[j + i] = s2;
carry = (c1 as u64) | (c2 as u64);
}
let (s_top, _ignored) = u_norm[j + n].overflowing_add(carry);
u_norm[j + n] = s_top;
} else {
q[j] = qhat_u64;
}
}
let mut rem_limbs: Vec<u64> = u_norm[..n].to_vec();
normalize(&mut rem_limbs);
let rem = BigUint { limbs: rem_limbs };
let rem_unshifted = if shift == 0 {
rem
} else {
rem.shr_bits(shift as u64)
};
normalize(&mut q);
(BigUint { limbs: q }, rem_unshifted)
}
pub const NEWTON_DIV_THRESHOLD: usize = 50;
const BZ_DIV_LIMB_THRESHOLD: usize = 24;
#[inline]
fn from_limbs_vec(mut limbs: Vec<u64>) -> BigUint {
normalize(&mut limbs);
BigUint { limbs }
}
#[inline]
fn limb_window(n: &BigUint, lo: usize, hi: usize) -> BigUint {
let len = n.limbs.len();
if lo >= hi || lo >= len {
return BigUint::zero();
}
let hi = hi.min(len);
from_limbs_vec(n.limbs[lo..hi].to_vec())
}
fn correct_quotient(u: &BigUint, d: &BigUint, q_est: BigUint) -> Option<(BigUint, BigUint)> {
let mut q = q_est;
let mut prod = &q * d;
let mut iters_down: u32 = 0;
while prod.cmp(u) == core::cmp::Ordering::Greater {
q = q.checked_sub(&BigUint::one())?;
prod = prod.checked_sub(d)?;
iters_down += 1;
}
debug_assert!(
iters_down <= 8,
"correct_quotient: down-correction ran {iters_down} iterations โ estimate grossly wrong"
);
let mut r = u.checked_sub(&prod)?;
let mut iters_up: u32 = 0;
while r.cmp(d) != core::cmp::Ordering::Less {
q = &q + &BigUint::one();
r = r.checked_sub(d)?;
iters_up += 1;
}
debug_assert!(
iters_up <= 8,
"correct_quotient: up-correction ran {iters_up} iterations โ estimate grossly wrong"
);
Some((q, r))
}
fn div_burnikel_ziegler(u: &BigUint, v: &BigUint) -> (BigUint, BigUint) {
debug_assert!(v.limbs.len() >= 2);
debug_assert!(u.cmp(v) != core::cmp::Ordering::Less);
match bz_divrem_inner(u, v) {
Some(qr) => qr,
None => div_knuth_d(u, v),
}
}
fn bz_divrem_inner(u: &BigUint, v: &BigUint) -> Option<(BigUint, BigUint)> {
let shift = v.limbs[v.limbs.len() - 1].leading_zeros() as u64;
let v_norm = v.shl_bits(shift);
let u_norm = u.shl_bits(shift);
let n = v_norm.limbs.len();
debug_assert_eq!(
n,
v.limbs.len(),
"normalization cannot grow a nonzero-top divisor"
);
let u_len = u_norm.limbs.len();
let n_blocks = u_len.div_ceil(n).max(1);
let mut rem = BigUint::zero();
let mut quotient = BigUint::zero();
for block_idx in (0..n_blocks).rev() {
let lo = block_idx * n;
let hi = lo + n;
let block = limb_window(&u_norm, lo, hi); let dividend_chunk = &shift_limbs(&rem, n) + █
let (q_block, r_block) = div_two_one(÷nd_chunk, &v_norm, n)?;
quotient = &shift_limbs("ient, n) + &q_block;
rem = r_block;
}
correct_quotient(u, v, quotient)
}
#[inline]
fn shift_limbs(n: &BigUint, k: usize) -> BigUint {
if n.is_zero() || k == 0 {
return n.clone();
}
let mut out: Vec<u64> = Vec::with_capacity(n.limbs.len() + k);
out.resize(k, 0);
out.extend_from_slice(&n.limbs);
from_limbs_vec(out)
}
#[inline]
fn base_pow(k: usize) -> BigUint {
let mut limbs = vec![0u64; k];
limbs.push(1);
from_limbs_vec(limbs)
}
fn div_two_one(a: &BigUint, b: &BigUint, n: usize) -> Option<(BigUint, BigUint)> {
debug_assert_eq!(b.limbs.len(), n);
debug_assert!(
b.limbs[n - 1] >= 1u64 << 63,
"div_two_one needs a normalized divisor"
);
if n <= BZ_DIV_LIMB_THRESHOLD || n % 2 != 0 {
if a.cmp(b) == core::cmp::Ordering::Less {
return Some((BigUint::zero(), a.clone()));
}
return Some(div_knuth_d(a, b));
}
let half = n / 2;
let a_hi = limb_window(a, n, 2 * n);
let a_lo_hi = limb_window(a, half, n); let high_three = &shift_limbs(&a_hi, half) + &a_lo_hi; let (q1, r1) = div_three_two(&high_three, b, half)?;
let a_lo_lo = limb_window(a, 0, half); let low_three = &shift_limbs(&r1, half) + &a_lo_lo;
let (q0, r0) = div_three_two(&low_three, b, half)?;
let q = &shift_limbs(&q1, half) + &q0;
Some((q, r0))
}
fn div_three_two(a: &BigUint, b: &BigUint, half: usize) -> Option<(BigUint, BigUint)> {
let n = 2 * half;
debug_assert_eq!(b.limbs.len(), n);
let b1 = limb_window(b, half, n);
let b0 = limb_window(b, 0, half);
let a_hi2 = limb_window(a, half, 3 * half);
let a0 = limb_window(a, 0, half);
let beta = base_pow(half);
let (mut q, c) = if a_hi2.cmp(&(&b1 * &beta)) != core::cmp::Ordering::Less {
let q_max = beta.checked_sub(&BigUint::one())?;
let c = a_hi2.checked_sub(&(&b1 * &q_max))?; (q_max, c)
} else {
div_two_one_halfdiv(&a_hi2, &b1, half)?
};
let sub = &q * &b0;
let mut r_hi = &shift_limbs(&c, half) + &a0;
let mut guard: u32 = 0;
while r_hi.cmp(&sub) == core::cmp::Ordering::Less {
q = q.checked_sub(&BigUint::one())?;
r_hi = &r_hi + b;
guard += 1;
debug_assert!(guard <= 4, "div_three_two add-back exceeded the BZ bound");
}
let r = r_hi.checked_sub(&sub)?;
Some((q, r))
}
fn div_two_one_halfdiv(a: &BigUint, b1: &BigUint, half: usize) -> Option<(BigUint, BigUint)> {
debug_assert_eq!(b1.limbs.len(), half);
debug_assert!(
b1.limbs[half - 1] >= 1u64 << 63,
"halfdiv needs a normalized divisor"
);
if a.cmp(b1) == core::cmp::Ordering::Less {
return Some((BigUint::zero(), a.clone()));
}
div_two_one(a, b1, half)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_limb_basic() {
let u = BigUint::from_u64(100);
let v = BigUint::from_u64(7);
let (q, r) = divrem(&u, &v);
assert_eq!(q, BigUint::from_u64(14));
assert_eq!(r, BigUint::from_u64(2));
}
#[test]
fn single_limb_multilimb_dividend() {
let u = BigUint::from_le_limbs(&[0, 1]); let v = BigUint::from_u64(2);
let (q, r) = divrem(&u, &v);
assert_eq!(q, BigUint::from_le_limbs(&[1u64 << 63]));
assert!(r.is_zero());
}
#[test]
fn checked_div_by_zero() {
let u = BigUint::from_u64(10);
let v = BigUint::zero();
assert!(checked_divrem(&u, &v).is_none());
}
#[test]
fn dividend_smaller_than_divisor() {
let u = BigUint::from_u64(7);
let v = BigUint::from_le_limbs(&[0, 1]); let (q, r) = divrem(&u, &v);
assert!(q.is_zero());
assert_eq!(r, u);
}
#[test]
fn divisor_equals_dividend() {
let u = BigUint::from_le_limbs(&[u64::MAX, u64::MAX, 1]);
let (q, r) = divrem(&u, &u.clone());
assert_eq!(q, BigUint::from_u64(1));
assert!(r.is_zero());
}
#[test]
fn knuth_d_top_already_normalized() {
let v = BigUint::from_le_limbs(&[0xDEAD_BEEF_CAFE_BABEu64, 0x8000_0000_0000_0001u64]);
let u = BigUint::from_le_limbs(&[0, 0, 0, 1]); let (q, r) = divrem(&u, &v);
let back = &(&q * &v) + &r;
assert_eq!(back, u);
assert!(r < v);
}
#[test]
fn knuth_d_max_top_limb() {
let v = BigUint::from_le_limbs(&[1, u64::MAX]);
let u = BigUint::from_le_limbs(&[0, 0, 0, 1]);
let (q, r) = divrem(&u, &v);
let back = &(&q * &v) + &r;
assert_eq!(back, u);
assert!(r < v);
}
#[test]
fn power_of_two_divisor_matches_shr() {
let u = BigUint::from_le_limbs(&[0xDEAD_BEEF_CAFE_BABE, 0x1234_5678_9ABC_DEF0, 0x42]);
let v = BigUint::from_le_limbs(&[0, 0, 1]); let (q, r) = divrem(&u, &v);
let expected_q = u.shr_bits(128);
assert_eq!(q, expected_q);
let expected_r = BigUint::from_le_limbs(&[u.limbs[0], u.limbs[1]]);
assert_eq!(r, expected_r);
}
fn xorshift64(mut s: u64) -> u64 {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
}
fn rand_biguint_exact(state: &mut u64, n: usize) -> BigUint {
let mut limbs = Vec::with_capacity(n);
for _ in 0..n {
*state = xorshift64(*state);
limbs.push(*state);
}
if limbs[n - 1] == 0 {
limbs[n - 1] = 1;
}
BigUint::from_le_limbs(&limbs)
}
fn assert_bz_matches_knuth(u: &BigUint, v: &BigUint) {
let (qk, rk) = div_knuth_d(u, v);
let (qb, rb) = div_burnikel_ziegler(u, v);
assert_eq!(qb, qk, "quotient mismatch vs Knuth-D");
assert_eq!(rb, rk, "remainder mismatch vs Knuth-D");
let back = &(&qb * v) + &rb;
assert_eq!(&back, u, "u == q*v + r failed");
assert!(rb < *v, "remainder not < divisor");
}
#[test]
fn bz_corrector_fixes_bounded_estimate_errors() {
let u = BigUint::from_le_limbs(&[0xDEAD_BEEF, 0x1234, 0x99]);
let d = BigUint::from_le_limbs(&[0xCAFE_BABE, 0x55]);
let (q_true, r_true) = div_knuth_d(&u, &d);
for delta in [0u64, 1, 2, 3, 5] {
let over = &q_true + &BigUint::from_u64(delta);
let (q1, r1) = correct_quotient(&u, &d, over).expect("corrector");
assert_eq!(q1, q_true);
assert_eq!(r1, r_true);
let under = q_true
.checked_sub(&BigUint::from_u64(delta))
.unwrap_or_else(BigUint::zero);
let (q2, r2) = correct_quotient(&u, &d, under).expect("corrector");
assert_eq!(q2, q_true);
assert_eq!(r2, r_true);
}
}
#[test]
fn bz_corrector_exact_multiple_remainder_zero() {
let d = BigUint::from_le_limbs(&[0x12345, 0x6789A, 0xBCDEF]);
let q = BigUint::from_le_limbs(&[0xFFFF_0000, 0x1, 0xABCD]);
let u = &q * &d;
let (qc, rc) = correct_quotient(&u, &d, &q + &BigUint::from_u64(4)).expect("corrector");
assert_eq!(qc, q);
assert!(rc.is_zero());
}
#[test]
fn bz_three_two_matches_knuth_small() {
let half = 2usize;
let n = 2 * half;
let mut state = 0x0123_4567_89AB_CDEFu64;
let mut blimbs = Vec::with_capacity(n);
for _ in 0..n {
state = xorshift64(state);
blimbs.push(state);
}
blimbs[n - 1] |= 1u64 << 63; let b = BigUint::from_le_limbs(&blimbs);
let beta = base_pow(half);
let upper_bound = &b * β for _ in 0..50 {
let mut alimbs = Vec::with_capacity(3 * half);
for _ in 0..(3 * half) {
state = xorshift64(state);
alimbs.push(state);
}
let mut a = BigUint::from_le_limbs(&alimbs);
if a.cmp(&upper_bound) != core::cmp::Ordering::Less {
a = &a % &upper_bound;
}
let (q, r) = div_three_two(&a, &b, half).expect("three_two");
let (qk, rk) = if a.cmp(&b) == core::cmp::Ordering::Less {
(BigUint::zero(), a.clone())
} else {
div_knuth_d(&a, &b)
};
assert_eq!(q, qk, "three_two quotient mismatch");
assert_eq!(r, rk, "three_two remainder mismatch");
assert!(r < b);
}
}
#[test]
fn bz_two_one_matches_knuth_at_threshold() {
let n = (BZ_DIV_LIMB_THRESHOLD + 1) & !1; let n = if n <= BZ_DIV_LIMB_THRESHOLD { n + 2 } else { n };
let mut state = 0xFEDC_BA98_7654_3210u64;
let mut blimbs = Vec::with_capacity(n);
for _ in 0..n {
state = xorshift64(state);
blimbs.push(state);
}
blimbs[n - 1] |= 1u64 << 63; let b = BigUint::from_le_limbs(&blimbs);
let beta_n = base_pow(n);
let bound = &b * &beta_n;
for _ in 0..30 {
let mut alimbs = Vec::with_capacity(2 * n);
for _ in 0..(2 * n) {
state = xorshift64(state);
alimbs.push(state);
}
let mut a = BigUint::from_le_limbs(&alimbs);
if a.cmp(&bound) != core::cmp::Ordering::Less {
a = &a % &bound;
}
let (q, r) = div_two_one(&a, &b, n).expect("two_one");
let (qk, rk) = if a.cmp(&b) == core::cmp::Ordering::Less {
(BigUint::zero(), a.clone())
} else {
div_knuth_d(&a, &b)
};
assert_eq!(q, qk, "two_one quotient mismatch");
assert_eq!(r, rk, "two_one remainder mismatch");
assert!(r < b);
}
}
#[test]
fn bz_random_sweep_vs_knuth() {
let mut state = 0xA5A5_5A5A_C3C3_3C3Cu64;
for _ in 0..200 {
state = xorshift64(state);
let vlen = NEWTON_DIV_THRESHOLD + (state as usize % 13);
state = xorshift64(state);
let ulen = vlen + (state as usize % 40);
let v = rand_biguint_exact(&mut state, vlen);
let u = rand_biguint_exact(&mut state, ulen);
if u.cmp(&v) == core::cmp::Ordering::Less {
continue;
}
assert_bz_matches_knuth(&u, &v);
}
}
#[test]
fn bz_power_of_two_divisor() {
let k = NEWTON_DIV_THRESHOLD + 3;
let v = base_pow(k);
let mut state = 0x1357_9BDF_2468_ACE0u64;
let u = rand_biguint_exact(&mut state, k + 17);
assert_bz_matches_knuth(&u, &v);
}
#[test]
fn bz_divisor_one_bit_below_dividend() {
let mut state = 0x2468_ACE0_1357_9BDFu64;
let v = rand_biguint_exact(&mut state, NEWTON_DIV_THRESHOLD + 5);
let u = &v + &v.shr_bits(1); assert_bz_matches_knuth(&u, &v);
}
#[test]
fn bz_exact_multiple_remainder_zero_path() {
let mut state = 0x0F0F_F0F0_0F0F_F0F0u64;
let v = rand_biguint_exact(&mut state, NEWTON_DIV_THRESHOLD + 7);
let q = rand_biguint_exact(&mut state, 23);
let u = &q * &v;
let (qb, rb) = div_burnikel_ziegler(&u, &v);
assert_eq!(qb, q);
assert!(rb.is_zero());
assert_bz_matches_knuth(&u, &v);
}
#[test]
fn bz_top_limb_exactly_2_pow_63() {
let mut state = 0xDEAD_C0DE_FACE_B00Cu64;
let mut vlimbs = Vec::with_capacity(NEWTON_DIV_THRESHOLD + 1);
for _ in 0..(NEWTON_DIV_THRESHOLD + 1) {
state = xorshift64(state);
vlimbs.push(state);
}
vlimbs[NEWTON_DIV_THRESHOLD] = 1u64 << 63; let v = BigUint::from_le_limbs(&vlimbs);
let u = rand_biguint_exact(&mut state, NEWTON_DIV_THRESHOLD + 25);
if u.cmp(&v) != core::cmp::Ordering::Less {
assert_bz_matches_knuth(&u, &v);
}
}
#[test]
fn bz_top_limb_2_pow_63_plus_1() {
let mut state = 0xB00C_FACE_C0DE_DEADu64;
let mut vlimbs = Vec::with_capacity(NEWTON_DIV_THRESHOLD + 1);
for _ in 0..(NEWTON_DIV_THRESHOLD + 1) {
state = xorshift64(state);
vlimbs.push(state);
}
vlimbs[NEWTON_DIV_THRESHOLD] = (1u64 << 63) + 1; let v = BigUint::from_le_limbs(&vlimbs);
let u = rand_biguint_exact(&mut state, NEWTON_DIV_THRESHOLD + 25);
if u.cmp(&v) != core::cmp::Ordering::Less {
assert_bz_matches_knuth(&u, &v);
}
}
#[test]
fn bz_highly_asymmetric_1000_by_60() {
let mut state = 0x9E37_79B9_7F4A_7C15u64;
let v = rand_biguint_exact(&mut state, 60);
let u = rand_biguint_exact(&mut state, 1000);
assert_bz_matches_knuth(&u, &v);
}
#[test]
fn bz_dividend_just_above_threshold() {
let mut state = 0x1111_2222_3333_4444u64;
let v = rand_biguint_exact(&mut state, NEWTON_DIV_THRESHOLD);
let u = rand_biguint_exact(&mut state, NEWTON_DIV_THRESHOLD + 1);
if u.cmp(&v) != core::cmp::Ordering::Less {
assert_bz_matches_knuth(&u, &v);
}
}
}