use super::super::div::limbs::{
div_2by1, div_qr, div_qr_1, div_qr_2, invert_3by2, invert_limb, qr_itch,
};
use super::super::mul::limbs::{add_1, add_n, sqr, sqr_itch, sub_1, sub_n};
use super::{addmul_1, lshift, rshift};
const TWO_64: f64 = 18_446_744_073_709_551_616.0;
#[inline(always)]
pub(in super::super) fn sqrt_rem_u128(a: u128) -> (u64, u128) {
let (hi, lo) = ((a >> 64) as u64, a as u64);
if hi == 0 {
let (s, r) = sqrt_rem_u64(lo);
return (s, u128::from(r));
}
let sf = (hi as f64 * TWO_64 + lo as f64).sqrt();
let s0 = sf as u64;
let e = a.wrapping_sub(u128::from(s0) * u128::from(s0)) as i128;
let ef = ((e >> 64) as i64) as f64 * TWO_64 + (e as u64) as f64;
let step = (ef * (0.5 / sf.max(1.0))).floor() as i64;
let s = (i128::from(s0) + i128::from(step)).clamp(0, i128::from(u64::MAX)) as u64;
let r = a.wrapping_sub(u128::from(s) * u128::from(s)) as i128;
if r < 0 || r > 2 * i128::from(s) {
return corrected(a, s);
}
(s, r as u128)
}
#[inline(always)]
pub(in super::super) fn sqrt_rem_wide<const REM: bool>(a: [u64; 4]) -> (u128, [u64; 3]) {
let [a0, a1, a2, a3] = a;
debug_assert!(a3 != 0 || a2 != 0);
let (hi, lo) = (
u128::from(a3) << 64 | u128::from(a2),
u128::from(a1) << 64 | u128::from(a0),
);
let c = hi.leading_zeros() / 2;
let sh = 2 * c;
let (hi, lo) = (hi << sh | lo.checked_shr(128 - sh).unwrap_or(0), lo << sh);
let (b1, b0) = ((lo >> 64) as u64, lo as u64);
let (s1, r1) = sqrt_rem_u128(hi);
let d = u128::from(s1);
let (top, rr): (u64, u128) = if r1 >= 2 * d {
(2, r1 - 2 * d)
} else if r1 >= d {
(1, r1 - d)
} else {
(0, r1)
};
let (q0, u0) = div_2by1(rr as u64, b1, s1, invert_limb(s1));
let q = (u128::from(top) << 64 | u128::from(q0)) >> 1;
let u = u128::from(u0) + if q0 & 1 == 1 { d } else { 0 };
let mut root = (d << 64).wrapping_add(q);
let mask = (1u128 << c) - 1;
if !REM && c >= 2 && root & mask & !1 != 0 {
return (root >> c, [0; 3]);
}
let (qh, ql) = ((q >> 64) as u64, q as u64);
let p = u128::from(ql) * u128::from(ql);
let (r0, borrow0) = b0.overflowing_sub(p as u64);
let (mid, borrow1) = (u as u64).overflowing_sub((p >> 64) as u64);
let (mid, borrow2) = mid.overflowing_sub(u64::from(borrow0));
let r2 = ((u >> 64) as u64)
.wrapping_sub(qh)
.wrapping_sub(u64::from(borrow1 | borrow2));
let mut r = [r0, mid, r2];
if (r2 as i64) < 0 {
root = root.wrapping_sub(1);
let add = [
(root << 1) as u64 | 1,
(root >> 63) as u64,
(root >> 127) as u64,
];
let mut carry = false;
for (x, y) in r.iter_mut().zip(add) {
let (v, c1) = x.overflowing_add(y);
let (v, c2) = v.overflowing_add(u64::from(carry));
*x = v;
carry = c1 | c2;
}
}
if !REM {
return (root >> c, [0; 3]);
}
if c == 0 {
return (root, r);
}
let s0 = root & mask;
let m = 2 * s0 as u64;
let low = u128::from(root as u64) * u128::from(m);
let high = u128::from((root >> 64) as u64) * u128::from(m) + (low >> 64);
let sq = s0 * s0;
let mut acc = [0u64; 3];
let mut carry = 0u128;
for (i, (x, y)) in [
(r[0], low as u64),
(r[1], high as u64),
(r[2], (high >> 64) as u64),
]
.into_iter()
.enumerate()
{
let t = u128::from(x) + u128::from(y) + carry;
acc[i] = t as u64;
carry = t >> 64;
}
let mut borrow = 0u64;
for (i, y) in [sq as u64, (sq >> 64) as u64, 0].into_iter().enumerate() {
let (v, b1) = acc[i].overflowing_sub(y);
let (v, b2) = v.overflowing_sub(borrow);
acc[i] = v;
borrow = u64::from(b1 | b2);
}
let value = u128::from(acc[1]) << 64 | u128::from(acc[0]);
let top = acc[2];
let rem = (value >> sh) | (u128::from(top) << (128 - sh));
let rem_top = if sh >= 64 { 0 } else { top >> sh };
(root >> c, [rem as u64, (rem >> 64) as u64, rem_top])
}
#[inline(always)]
fn sqrt_rem_u64(a: u64) -> (u64, u64) {
let s = ((a as f64).sqrt() as u64).min(u64::from(u32::MAX));
let r = a.wrapping_sub(s * s) as i64;
if r < 0 || r as u64 > 2 * s {
let (s, r) = corrected(u128::from(a), s);
return (s, r as u64);
}
(s, r as u64)
}
#[cold]
#[inline(never)]
fn corrected(a: u128, mut s: u64) -> (u64, u128) {
let mut r = a.wrapping_sub(u128::from(s) * u128::from(s)) as i128;
while r < 0 {
r += 2 * i128::from(s) - 1;
s -= 1;
}
while r > 2 * i128::from(s) {
s += 1;
r -= 2 * i128::from(s) - 1;
}
(s, r as u128)
}
unsafe fn divide(
q: *mut u64,
n: *mut u64,
nn: usize,
d: *const u64,
dn: usize,
t: *mut u64,
) -> u64 {
unsafe {
match dn {
1 => {
let d0 = *d;
let mut top = *n.add(nn - 1);
let qh = top >= d0;
if qh {
top -= d0;
}
*n = div_qr_1::<true>(q, n, nn - 1, top, d0, invert_limb(d0));
u64::from(qh)
}
2 => {
let (d1, d0) = (*d.add(1), *d);
let (t1, t0) = (*n.add(nn - 1), *n.add(nn - 2));
let qh = (t1, t0) >= (d1, d0);
if qh {
let (low, borrow) = t0.overflowing_sub(d0);
*n.add(nn - 2) = low;
*n.add(nn - 1) = t1 - d1 - u64::from(borrow);
}
let (r1, r0) = div_qr_2(q, n, nn, d1, d0, invert_3by2(d1, d0));
(*n, *n.add(1)) = (r0, r1);
u64::from(qh)
}
_ => {
let v = invert_3by2(*d.add(dn - 1), *d.add(dn - 2));
u64::from(div_qr(q, n, nn, d, dn, v, t))
}
}
}
}
const SMALL_ITCH: usize = 64;
pub(super) fn dc_itch(n: usize) -> usize {
if n < SMALL_ITCH {
debug_assert!(qr_itch(n, n - n / 2) == 0 && sqr_itch(n / 2) == 0);
return n;
}
let (l, h) = (n / 2, n - n / 2);
let division = l + if h >= 3 { qr_itch(n, h) } else { 0 };
dc_itch(h).max(division).max(2 * l + sqr_itch(l))
}
pub(super) unsafe fn dc_sqrtrem(
s: *mut u64,
a: *mut u64,
n: usize,
approx: u64,
t: *mut u64,
) -> u64 {
unsafe {
if n == 1 {
let (root, r) = sqrt_rem_u128(u128::from(*a.add(1)) << 64 | u128::from(*a));
*s = root;
*a = r as u64;
return (r >> 64) as u64;
}
if n == 2 {
let (root, r) = sqrt_rem_wide::<true>([*a, *a.add(1), *a.add(2), *a.add(3)]);
(*s, *s.add(1)) = (root as u64, (root >> 64) as u64);
(*a, *a.add(1)) = (r[0], r[1]);
return r[2];
}
let l = n / 2;
let h = n - l;
let mut q = dc_sqrtrem(s.add(l), a.add(2 * l), h, 0, t);
if q != 0 {
sub_n(a.add(2 * l), a.add(2 * l), s.add(l), h);
}
q += divide(t, a.add(l), n, s.add(l), h, t.add(l));
let odd = *t & 1;
for i in 0..l - 1 {
*s.add(i) = (*t.add(i) >> 1) | (*t.add(i + 1) << 63);
}
*s.add(l - 1) = (*t.add(l - 1) >> 1) | ((q & 1) << 63);
if *s & approx != 0 {
return 1;
}
q >>= 1;
let mut c: i64 = 0;
if odd != 0 {
c = i64::from(add_n(a.add(l), a.add(l), s.add(l), h));
}
sqr(t, s, l, t.add(2 * l));
let borrow = q + u64::from(sub_n(a, a, t, 2 * l));
c -= if l == h {
borrow as i64
} else {
sub_1(a.add(2 * l), 1, borrow) as i64
};
if c < 0 {
let carry = add_1(s.add(l), h, q);
c += i64::from(add_n(a, a, s, n)) + i64::from(add_n(a, a, s, n)) + 2 * carry as i64;
c -= sub_1(a, n, 1) as i64;
sub_1(s, n, 1);
}
c as u64
}
}
pub(super) fn sqrtrem_itch(an: usize) -> usize {
let tn = an.div_ceil(2) + 1;
2 * tn + 1 + dc_itch(tn)
}
pub(super) const PAD: usize = 12;
pub(super) unsafe fn sqrtrem<const REM: bool>(
s: *mut u64,
r: *mut u64,
a: *const u64,
an: usize,
w: *mut u64,
) -> usize {
debug_assert!(an >= 3);
unsafe {
let c = (*a.add(an - 1)).leading_zeros() / 2;
let odd = an & 1;
let tn = an.div_ceil(2);
let low = if !REM && odd == 0 && c < 2 && tn >= PAD {
2
} else {
odd
};
let n = (an + low) / 2;
let (x, t) = (w, w.add(2 * n + 1));
core::ptr::write_bytes(x, 0, low);
lshift(x.add(low), a, an, 2 * c);
let k = c + 32 * low as u32;
let mask = if k < 64 { (1u64 << k) - 1 } else { u64::MAX };
let approx = if REM { 0 } else { mask & !1 };
let top = dc_sqrtrem(s, x, n, approx, t);
if !REM {
if k >= 64 {
core::ptr::copy(s.add(1), s, n - 1);
}
if !k.is_multiple_of(64) {
rshift(s, s, tn, k % 64);
}
return 0;
}
let rn = if k == 0 {
core::ptr::copy_nonoverlapping(x, r, tn);
*r.add(tn) = top;
tn + 1
} else {
let s0 = *s & mask;
let mut top = top + addmul_1(x, s, tn, 2 * s0);
let p = u128::from(s0) * u128::from(s0);
let (low, b0) = (*x).overflowing_sub(p as u64);
let (high, b1) = (*x.add(1)).overflowing_sub((p >> 64) as u64);
let (high, b2) = high.overflowing_sub(u64::from(b0));
(*x, *x.add(1)) = (low, high);
top -= sub_1(x.add(2), tn - 2, u64::from(b1 | b2));
*x.add(tn) = top;
rshift(s, s, tn, k);
let (whole, bits) = ((2 * k / 64) as usize, 2 * k % 64);
let rn = tn + 1 - whole;
if bits == 0 {
core::ptr::copy_nonoverlapping(x.add(whole), r, rn);
} else {
rshift(r, x.add(whole), rn, bits);
}
rn
};
let mut rn = rn;
while rn > 0 && *r.add(rn - 1) == 0 {
rn -= 1;
}
rn
}
}