use crate::{LIMBS, U256};
use core::mem::MaybeUninit;
pub(crate) const WIDE_LIMBS: usize = 2 * LIMBS;
const B: u64 = 1 << 32;
const HB: u32 = 1 << 16;
#[inline]
const fn div_rem_2by1(hi: u32, lo: u32, d: u32) -> (u32, u32) {
debug_assert!(hi < d);
let s = d.leading_zeros();
let un32 = if s == 0 {
hi
} else {
(hi << s) | (lo >> (32 - s))
};
let (q, rem) = div_rem_2by1_normalized(un32, lo << s, d << s);
(q, rem >> s)
}
#[inline]
const fn div_rem_2by1_normalized(hi: u32, lo: u32, d: u32) -> (u32, u32) {
debug_assert!(hi < d);
debug_assert!(d >> 31 == 1);
let vn1 = d >> 16;
let vn0 = d & 0xffff;
let un1 = lo >> 16;
let un0 = lo & 0xffff;
let mut q1 = hi / vn1;
let mut rhat = hi - q1 * vn1;
while q1 >= HB || q1 * vn0 > (rhat << 16) | un1 {
q1 -= 1;
rhat += vn1;
if rhat >= HB {
break;
}
}
let un21 = hi
.wrapping_mul(HB)
.wrapping_add(un1)
.wrapping_sub(q1.wrapping_mul(d));
let mut q0 = un21 / vn1;
rhat = un21 - q0 * vn1;
while q0 >= HB || q0 * vn0 > (rhat << 16) | un0 {
q0 -= 1;
rhat += vn1;
if rhat >= HB {
break;
}
}
let rem = un21
.wrapping_mul(HB)
.wrapping_add(un0)
.wrapping_sub(q0.wrapping_mul(d));
(q1 * HB + q0, rem)
}
impl U256 {
#[inline]
pub const fn div_rem_small(self, rhs: u32) -> (Self, u32) {
self.div_rem_small_from(rhs, 0)
}
#[inline]
pub(crate) const fn div_rem_small_from(
self,
rhs: u32,
rem: u32,
) -> (Self, u32) {
if rhs == 0 {
panic!("attempt to divide by zero");
}
debug_assert!(rem < rhs);
let mut q = [0u32; LIMBS];
let mut rem = rem;
let mut i = LIMBS;
while i > 0 {
i -= 1;
let (qi, r) = div_rem_2by1(rem, self.limbs[i], rhs);
q[i] = qi;
rem = r;
}
(Self::from_limbs(q), rem)
}
pub const fn div_rem(self, rhs: Self) -> (Self, Self) {
match self.checked_div_rem(rhs) {
Some(pair) => pair,
None => panic!("attempt to divide by zero"),
}
}
pub const fn checked_div_rem(self, rhs: Self) -> Option<(Self, Self)> {
let dn = rhs.limb_len();
if dn == 0 {
return None;
}
let nn = self.limb_len();
if nn < dn {
return Some((Self::ZERO, self));
}
if dn == 1 {
let (q, r) = self.div_rem_small(rhs.limbs[0]);
return Some((q, Self::from_u32(r)));
}
let (q, r) = div_rem_knuth(&self.limbs, nn, &rhs.limbs, dn);
Some((Self::from_limbs(q), r))
}
#[inline]
pub const fn checked_div(self, rhs: Self) -> Option<Self> {
match self.checked_div_rem(rhs) {
Some((q, _)) => Some(q),
None => None,
}
}
#[inline]
pub const fn checked_rem(self, rhs: Self) -> Option<Self> {
match self.checked_div_rem(rhs) {
Some((_, r)) => Some(r),
None => None,
}
}
}
#[inline]
pub(crate) const fn low_limbs(wide: &[u32; WIDE_LIMBS]) -> [u32; LIMBS] {
let mut out = [0u32; LIMBS];
let mut i = 0;
while i < LIMBS {
out[i] = wide[i];
i += 1;
}
out
}
macro_rules! knuth_d {
($name:ident, $n:expr) => {
const fn $name(
u: &[u32; $n],
nn: usize,
v: &[u32; LIMBS],
dn: usize,
) -> ([u32; $n], U256) {
debug_assert!(dn >= 2);
debug_assert!(nn >= dn);
debug_assert!(nn <= $n);
let s = v[dn - 1].leading_zeros();
let mut vn: [MaybeUninit<u32>; LIMBS] =
[MaybeUninit::uninit(); LIMBS];
let mut i = dn;
while i > 0 {
i -= 1;
let lo = if i == 0 || s == 0 {
0
} else {
v[i - 1] >> (32 - s)
};
vn[i] = MaybeUninit::new((v[i] << s) | lo);
}
let mut un: [MaybeUninit<u32>; $n + 1] =
[MaybeUninit::uninit(); $n + 1];
un[nn] = MaybeUninit::new(if s == 0 {
0
} else {
u[nn - 1] >> (32 - s)
});
let mut i = nn;
while i > 0 {
i -= 1;
let lo = if i == 0 || s == 0 {
0
} else {
u[i - 1] >> (32 - s)
};
un[i] = MaybeUninit::new((u[i] << s) | lo);
}
let mut q = [0u32; $n];
let mut j = nn - dn + 1;
while j > 0 {
j -= 1;
let hi = unsafe { un[j + dn].assume_init() };
let lo = unsafe { un[j + dn - 1].assume_init() };
let dv = unsafe { vn[dn - 1].assume_init() };
let (mut qhat, mut rhat) = if hi < dv {
let (qh, rh) = div_rem_2by1_normalized(hi, lo, dv);
(qh as u64, rh as u64)
} else {
debug_assert!(hi == dv);
(B - 1, (dv as u64) + (lo as u64))
};
let vn_second = unsafe { vn[dn - 2].assume_init() };
let un_third = unsafe { un[j + dn - 2].assume_init() };
while rhat < B
&& qhat * (vn_second as u64)
> (rhat << 32) | (un_third as u64)
{
qhat -= 1;
rhat += dv as u64;
}
let mut k: i64 = 0;
let mut i = 0;
while i < dn {
let vi = unsafe { vn[i].assume_init() };
let uij = unsafe { un[i + j].assume_init() };
let p = qhat * (vi as u64);
let t = (uij as i64) - k - ((p & 0xffff_ffff) as i64);
un[i + j] = MaybeUninit::new(t as u32);
k = ((p >> 32) as i64) - (t >> 32);
i += 1;
}
let t = (unsafe { un[j + dn].assume_init() } as i64) - k;
un[j + dn] = MaybeUninit::new(t as u32);
q[j] = qhat as u32;
if t < 0 {
q[j] -= 1;
let mut carry = 0u64;
let mut i = 0;
while i < dn {
let vi = unsafe { vn[i].assume_init() };
let uij = unsafe { un[i + j].assume_init() };
let sum = (uij as u64) + (vi as u64) + carry;
un[i + j] = MaybeUninit::new(sum as u32);
carry = sum >> 32;
i += 1;
}
let top = unsafe { un[j + dn].assume_init() };
un[j + dn] = MaybeUninit::new(
(top as u64).wrapping_add(carry) as u32,
);
}
}
let mut r = [0u32; LIMBS];
let mut i = 0;
while i < dn {
let lo = unsafe { un[i].assume_init() };
let next = unsafe { un[i + 1].assume_init() };
let hi = if s == 0 { 0 } else { next << (32 - s) };
r[i] = (lo >> s) | hi;
i += 1;
}
(q, U256::from_limbs(r))
}
};
}
knuth_d!(div_rem_knuth, LIMBS);
knuth_d!(div_rem_knuth_wide, WIDE_LIMBS);
pub(crate) const fn div_rem_512(
hi: U256,
lo: U256,
d: U256,
) -> Option<([u32; WIDE_LIMBS], U256)> {
let dn = d.limb_len();
if dn == 0 {
return None;
}
if dn == 1 {
let divisor = d.limbs[0];
let (qhi, rem) = hi.div_rem_small(divisor);
let (qlo, rem) = lo.div_rem_small_from(divisor, rem);
let mut q = [0u32; WIDE_LIMBS];
let mut i = 0;
while i < LIMBS {
q[i] = qlo.limbs[i];
q[i + LIMBS] = qhi.limbs[i];
i += 1;
}
return Some((q, U256::from_u32(rem)));
}
let mut u = [0u32; WIDE_LIMBS];
let mut i = 0;
while i < LIMBS {
u[i] = lo.limbs[i];
u[i + LIMBS] = hi.limbs[i];
i += 1;
}
let mut nn = WIDE_LIMBS;
while nn > 0 && u[nn - 1] == 0 {
nn -= 1;
}
if nn < dn {
return Some(([0u32; WIDE_LIMBS], lo));
}
let (q, r) = div_rem_knuth_wide(&u, nn, &d.limbs, dn);
Some((q, r))
}
#[inline]
pub fn mul_div_u128(a: u128, b: u128, d: u128) -> u128 {
match checked_mul_div_u128(a, b, d) {
Some(v) => v,
None if d == 0 => panic!("attempt to divide by zero"),
None => panic!("mul_div_u128 quotient exceeds u128"),
}
}
#[inline]
pub fn checked_mul_div_u128(a: u128, b: u128, d: u128) -> Option<u128> {
let (q, _) =
U256::widening_mul_u128(a, b).checked_div_rem(U256::from(d))?;
u128::try_from(q).ok()
}
#[cfg(test)]
mod tests {
use super::*;
fn reference(hi: u32, lo: u32, d: u32) -> (u32, u32) {
let n = ((hi as u64) << 32) | (lo as u64);
((n / d as u64) as u32, (n % d as u64) as u32)
}
fn check(hi: u32, lo: u32, d: u32) {
assert!(hi < d, "precondition");
assert_eq!(
div_rem_2by1(hi, lo, d),
reference(hi, lo, d),
"({hi}, {lo}) / {d}"
);
if d >> 31 == 1 {
assert_eq!(
div_rem_2by1_normalized(hi, lo, d),
reference(hi, lo, d),
"normalized ({hi}, {lo}) / {d}"
);
}
}
#[test]
fn div_rem_2by1_matches_u64_on_boundaries() {
let interesting = [
1u32,
2,
3,
10,
1_000_000_000,
0xffff,
0x1_0000,
0x8000_0000,
0xffff_fffe,
u32::MAX,
];
let los = [0u32, 1, 0xffff, 0x1_0000, 0x8000_0000, u32::MAX];
for d in interesting {
for lo in los {
for hi in [0, d / 2, d.saturating_sub(1)] {
if hi < d {
check(hi, lo, d);
}
}
}
}
}
#[test]
fn div_rem_2by1_matches_u64_on_random_inputs() {
let mut x = 0x2545_f491_4f6c_dd1du64;
let mut next = move || {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x
};
for _ in 0..200_000 {
let d = (next() as u32).max(1);
let hi = (next() as u32) % d;
let lo = next() as u32;
check(hi, lo, d);
}
}
#[test]
fn div_rem_2by1_handles_unnormalised_and_normalised_divisors() {
let mut x = 0x9e37_79b9_7f4a_7c15u64;
let mut next = move || {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x
};
for _ in 0..50_000 {
let d = (next() as u32) | 0x8000_0000;
check((next() as u32) % d, next() as u32, d);
check(0, next() as u32, 1);
}
}
}