pub(crate) const POW10_U128: [u128; 39] = {
let mut t = [1u128; 39];
let mut i = 1;
while i < 39 {
t[i] = t[i - 1] * 10;
i += 1;
}
t
};
const fn mg_reciprocal(d: u128) -> u128 {
let s = d.leading_zeros();
let d_norm = d << s;
let mut quot_lo: u128 = 0; let mut quot_hi: u128 = 0; let mut rem: u128 = 0;
let mut pos: i32 = 256;
while pos >= 0 {
let rem_carry = rem >> 127;
let shifted = (rem << 1) | if pos == 256 { 1 } else { 0 };
let fits = rem_carry == 1 || shifted >= d_norm;
rem = if fits {
shifted.wrapping_sub(d_norm)
} else {
shifted
};
quot_hi = (quot_hi << 1) | (quot_lo >> 127);
quot_lo = (quot_lo << 1) | (fits as u128);
pos -= 1;
}
assert!(
quot_hi == 1,
"MG reciprocal: normalised quotient must be 129-bit"
);
quot_lo
}
const MG_EXP_MAGICS: [(u128, u32); 39] = {
let mut table = [(0u128, 0u32); 39];
let mut i = 1;
while i < 39 {
let d = POW10_U128[i];
table[i] = (mg_reciprocal(d), d.leading_zeros());
i += 1;
}
table
};
#[inline]
pub(crate) const fn mul_u128_to_u256(a: u128, b: u128) -> (u128, u128) {
const LOW64: u128 = u64::MAX as u128;
let (a_lo, a_hi) = (a & LOW64, a >> 64);
let (b_lo, b_hi) = (b & LOW64, b >> 64);
let p00 = a_lo * b_lo;
let p01 = a_lo * b_hi;
let p10 = a_hi * b_lo;
let p11 = a_hi * b_hi;
let mid = (p00 >> 64) + (p01 & LOW64) + (p10 & LOW64);
let low = (p00 & LOW64) | (mid << 64);
let high = p11 + (p01 >> 64) + (p10 >> 64) + (mid >> 64);
(high, low)
}
#[inline]
pub(crate) fn divmod_pow10_2word(
n_high: u128,
n_low: u128,
exp: u128,
scale_idx: usize,
) -> Option<(u128, u128)> {
if n_high >= exp {
return None;
}
let (recip, s) = MG_EXP_MAGICS[scale_idx];
let top = (n_high << s) | (n_low >> (128 - s));
let bottom = n_low << s;
let (hi_from_top, lo_from_top) = mul_u128_to_u256(recip, top);
let (carry_from_bottom, _) = mul_u128_to_u256(recip, bottom);
let (acc_low, c0) = lo_from_top.overflowing_add(carry_from_bottom);
let acc_high = hi_from_top + u128::from(c0);
let (_, c1) = acc_low.overflowing_add(bottom);
let q = acc_high + top + u128::from(c1);
let (_, prod_low) = mul_u128_to_u256(q, exp);
let (rem, under) = n_low.overflowing_sub(prod_low);
debug_assert!(n_high == mul_u128_to_u256(q, exp).0 + u128::from(under));
if rem < exp {
Some((q, rem))
} else {
Some((q + 1, rem - exp))
}
}
#[inline]
pub(crate) fn div_pow10_mag_u128(
mag: &mut [u128],
scale: u32,
neg: bool,
mode: crate::support::rounding::RoundingMode,
) {
debug_assert!((1..=38).contains(&scale));
let scale_idx = scale as usize;
let exp = POW10_U128[scale_idx];
let mut top = mag.len();
while top > 0 && mag[top - 1] == 0 {
top -= 1;
}
let mut rem: u128 = 0;
let mut i = top;
while i > 0 {
i -= 1;
let limb = mag[i];
let (q_limb, r_limb) = divmod_pow10_2word(rem, limb, exp, scale_idx)
.expect("MG: rem < exp invariant violated");
mag[i] = q_limb;
rem = r_limb;
}
if rem != 0 {
let q_is_odd = (mag[0] & 1) != 0;
let comp = exp - rem;
let cmp_r = rem.cmp(&comp);
if crate::support::rounding::should_bump(mode, cmp_r, q_is_odd, !neg) {
let mut carry: u128 = 1;
for limb in mag.iter_mut() {
let (s, c) = limb.overflowing_add(carry);
*limb = s;
if !c {
carry = 0;
break;
}
carry = 1;
}
let _ = carry;
}
}
}
#[inline]
pub(crate) fn div_pow10_chain_mag_u128(
mag: &mut [u128],
scale: u32,
neg: bool,
mode: crate::support::rounding::RoundingMode,
) {
debug_assert!(
scale > 38,
"chain path is for SCALE > 38; callers handle smaller scales"
);
let exp38 = POW10_U128[38];
let mut top = mag.len();
while top > 0 && mag[top - 1] == 0 {
top -= 1;
}
let mut lower_any_nonzero = false;
let mut remaining = scale;
while remaining > 38 {
let mut rem: u128 = 0;
let mut i = top;
while i > 0 {
i -= 1;
let (q, r) = divmod_pow10_2word(rem, mag[i], exp38, 38)
.expect("MG chain: rem < exp invariant violated");
mag[i] = q;
rem = r;
}
if rem != 0 {
lower_any_nonzero = true;
}
remaining -= 38;
while top > 0 && mag[top - 1] == 0 {
top -= 1;
}
}
let scale_idx = remaining as usize;
let exp_last = POW10_U128[scale_idx];
let mut r_last: u128 = 0;
let mut i = top;
while i > 0 {
i -= 1;
let (q, r) = divmod_pow10_2word(r_last, mag[i], exp_last, scale_idx)
.expect("MG chain: rem < exp invariant violated");
mag[i] = q;
r_last = r;
}
let combined_nonzero = r_last != 0 || lower_any_nonzero;
if combined_nonzero {
let half = exp_last / 2; let cmp_r = if r_last > half {
core::cmp::Ordering::Greater
} else if r_last < half {
core::cmp::Ordering::Less
} else if lower_any_nonzero {
core::cmp::Ordering::Greater
} else {
core::cmp::Ordering::Equal
};
let q_is_odd = (mag[0] & 1) != 0;
if crate::support::rounding::should_bump(mode, cmp_r, q_is_odd, !neg) {
let mut carry: u128 = 1;
for limb in mag.iter_mut() {
let (s, c) = limb.overflowing_add(carry);
*limb = s;
if !c {
carry = 0;
break;
}
}
let _ = carry;
}
}
}
#[inline]
pub(crate) fn div_wide_pow10<W>(
n: W,
scale: u32,
mode: crate::support::rounding::RoundingMode,
) -> W
where
W: crate::int::types::traits::BigInt,
W::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let mut buf = <W::Scratch as crate::int::types::compute_limbs::ComputeLimbs>::single_u128();
let mag = &mut buf.as_mut()[..W::U128_LIMBS];
let neg = n.mag_into_u128(mag);
div_pow10_mag_u128(mag, scale, neg, mode);
W::from_mag_sign_u128(mag, neg)
}
#[inline]
pub(crate) fn div_wide_pow10_chain<W>(
n: W,
scale: u32,
mode: crate::support::rounding::RoundingMode,
) -> W
where
W: crate::int::types::traits::BigInt,
W::Scratch: crate::int::types::compute_limbs::ComputeLimbs,
{
let mut buf = <W::Scratch as crate::int::types::compute_limbs::ComputeLimbs>::single_u128();
let mag = &mut buf.as_mut()[..W::U128_LIMBS];
let neg = n.mag_into_u128(mag);
div_pow10_chain_mag_u128(mag, scale, neg, mode);
W::from_mag_sign_u128(mag, neg)
}
#[inline]
fn round_mag_with_mode(
q: u128,
r: u128,
m: u128,
mode: crate::support::rounding::RoundingMode,
result_positive: bool,
) -> u128 {
if r == 0 {
return q;
}
let comp = m - r;
let cmp_r = r.cmp(&comp);
let q_is_odd = (q & 1) != 0;
if crate::support::rounding::should_bump(mode, cmp_r, q_is_odd, result_positive) {
q + 1
} else {
q
}
}
#[inline]
fn div_long_256_by_128(n_high: u128, n_low: u128, d: u128) -> Option<u128> {
div_long_256_by_128_with_rem(n_high, n_low, d).map(|(q, _)| q)
}
#[inline]
fn div_long_256_by_128_with_rem(
mut n_high: u128,
mut n_low: u128,
d: u128,
) -> Option<(u128, u128)> {
if d == 0 {
return None;
}
if n_high == 0 {
return Some((n_low / d, n_low % d));
}
if n_high >= d {
return None;
}
if d <= u128::from(u64::MAX) {
let limbs = [
n_low as u64,
(n_low >> 64) as u64,
n_high as u64,
(n_high >> 64) as u64,
];
let mut out = [0u64; 4];
let mut rem: u128 = 0;
let mut i = 4;
while i > 0 {
i -= 1;
let cur = (rem << 64) | u128::from(limbs[i]);
out[i] = (cur / d) as u64;
rem = cur % d;
}
return Some((u128::from(out[0]) | (u128::from(out[1]) << 64), rem));
}
let bits = if n_high != 0 {
256 - n_high.leading_zeros()
} else {
128 - n_low.leading_zeros()
};
let shift = 256 - bits;
if shift >= 128 {
n_high = n_low << (shift - 128);
n_low = 0;
} else if shift > 0 {
n_high = (n_high << shift) | (n_low >> (128 - shift));
n_low <<= shift;
}
let mut q: u128 = 0;
let mut rem: u128 = 0;
let mut i = bits;
while i > 0 {
i -= 1;
rem = (rem << 1) | (n_high >> 127);
n_high = (n_high << 1) | (n_low >> 127);
n_low <<= 1;
q <<= 1;
if rem >= d {
rem -= d;
q |= 1;
}
}
Some((q, rem))
}
pub(crate) fn isqrt_256(hi: u128, lo: u128) -> u128 {
if hi == 0 && lo == 0 {
return 0;
}
let bits = if hi != 0 {
256 - hi.leading_zeros()
} else {
128 - lo.leading_zeros()
};
let n_limbs = [lo as u64, (lo >> 64) as u64, hi as u64, (hi >> 64) as u64];
let mut seed_limbs = [0u64; 4];
crate::algo_x_support::seed::sqrt_seed(&n_limbs, bits, &mut seed_limbs);
let mut q: u128 = (seed_limbs[0] as u128) | ((seed_limbs[1] as u128) << 64);
loop {
let nq = div_long_256_by_128(hi, lo, q)
.expect("isqrt_256: q >= sqrt(N), so N/q fits in 128 bits");
let q_next = (q >> 1) + (nq >> 1) + (q & nq & 1);
if q_next >= q {
return q;
}
q = q_next;
}
}
pub(crate) fn sqrt_raw_correctly_rounded(r: u128, scale: u32) -> u128 {
sqrt_raw_with(r, scale, crate::support::rounding::RoundingMode::HalfToEven)
}
pub(crate) fn sqrt_raw_with(
r: u128,
scale: u32,
mode: crate::support::rounding::RoundingMode,
) -> u128 {
use crate::support::rounding::RoundingMode;
if r == 0 {
return 0;
}
let scale_idx = scale as usize;
let pow = POW10_U128[scale_idx];
if r <= u128::MAX / pow {
let n = r * pow;
let q = n.isqrt();
let diff = n - q * q;
let diff_nonzero = diff != 0;
let halfway_round_up = diff > q;
let bump = match mode {
RoundingMode::HalfToEven
| RoundingMode::HalfAwayFromZero
| RoundingMode::HalfTowardZero => halfway_round_up,
RoundingMode::Trunc | RoundingMode::Floor => false,
RoundingMode::Ceiling => diff_nonzero,
};
return if bump { q + 1 } else { q };
}
let (hi, lo) = mul_u128_to_u256(r, pow);
let q = isqrt_256(hi, lo);
let (q_sq_hi, q_sq_lo) = mul_u128_to_u256(q, q);
let (diff_hi, diff_lo) = if lo >= q_sq_lo {
(hi - q_sq_hi, lo - q_sq_lo)
} else {
(hi - q_sq_hi - 1, lo.wrapping_sub(q_sq_lo))
};
let diff_nonzero = diff_hi != 0 || diff_lo != 0;
let halfway_round_up = diff_hi != 0 || diff_lo > q;
let bump = match mode {
RoundingMode::HalfToEven
| RoundingMode::HalfAwayFromZero
| RoundingMode::HalfTowardZero => halfway_round_up,
RoundingMode::Trunc | RoundingMode::Floor => false,
RoundingMode::Ceiling => diff_nonzero,
};
if bump { q + 1 } else { q }
}
fn pow10_256(exp: u32) -> [u128; 2] {
if exp <= 38 {
[10u128.pow(exp), 0]
} else {
let (hi, lo) = mul_u128_to_u256(10u128.pow(38), 10u128.pow(exp - 38));
[lo, hi]
}
}
fn mul_u128_by_256(a: u128, m: [u128; 2]) -> [u128; 3] {
let (p0_hi, p0_lo) = mul_u128_to_u256(a, m[0]);
let (p1_hi, p1_lo) = mul_u128_to_u256(a, m[1]);
let limb0 = p0_lo;
let (limb1, c1) = p0_hi.overflowing_add(p1_lo);
let limb2 = p1_hi + u128::from(c1);
[limb0, limb1, limb2]
}
fn mul_u256_by_u128(s: [u128; 2], b: u128) -> [u128; 3] {
let (p0_hi, p0_lo) = mul_u128_to_u256(s[0], b);
let (p1_hi, p1_lo) = mul_u128_to_u256(s[1], b);
let limb0 = p0_lo;
let (limb1, c1) = p0_hi.overflowing_add(p1_lo);
let limb2 = p1_hi + u128::from(c1);
[limb0, limb1, limb2]
}
fn shl3_384(n: [u128; 3]) -> [u128; 3] {
[
n[0] << 3,
(n[1] << 3) | (n[0] >> 125),
(n[2] << 3) | (n[1] >> 125),
]
}
fn ge_384(a: [u128; 3], b: [u128; 3]) -> bool {
if a[2] != b[2] {
a[2] > b[2]
} else if a[1] != b[1] {
a[1] > b[1]
} else {
a[0] >= b[0]
}
}
fn ge_256(a: [u128; 2], b: [u128; 2]) -> bool {
a[1] > b[1] || (a[1] == b[1] && a[0] >= b[0])
}
fn sub_256(a: [u128; 2], b: [u128; 2]) -> [u128; 2] {
let (lo, borrow) = a[0].overflowing_sub(b[0]);
let hi = a[1] - b[1] - u128::from(borrow);
[lo, hi]
}
fn div_384_by_256(mut num: [u128; 3], d: [u128; 2]) -> u128 {
let mut rem: [u128; 2] = [0, 0];
let mut q: u128 = 0;
let mut i = 0;
while i < 384 {
let num_top = num[2] >> 127;
num[2] = (num[2] << 1) | (num[1] >> 127);
num[1] = (num[1] << 1) | (num[0] >> 127);
num[0] <<= 1;
rem[1] = (rem[1] << 1) | (rem[0] >> 127);
rem[0] = (rem[0] << 1) | num_top;
q <<= 1;
if ge_256(rem, d) {
rem = sub_256(rem, d);
q |= 1;
}
i += 1;
}
q
}
fn floor_div3(mut carry: u128, mut val: u128) -> u128 {
const K: u128 = u128::MAX / 3;
let mut q: u128 = 0;
loop {
if carry == 0 {
return q + val / 3;
}
q += carry * K;
let (next_val, c) = val.overflowing_add(carry);
carry = u128::from(c);
val = next_val;
}
}
fn icbrt_384(n: [u128; 3]) -> u128 {
if n == [0, 0, 0] {
return 0;
}
let bits = if n[2] != 0 {
384 - n[2].leading_zeros()
} else if n[1] != 0 {
256 - n[1].leading_zeros()
} else {
128 - n[0].leading_zeros()
};
let n_limbs = [
n[0] as u64,
(n[0] >> 64) as u64,
n[1] as u64,
(n[1] >> 64) as u64,
n[2] as u64,
(n[2] >> 64) as u64,
];
let mut seed_limbs = [0u64; 6];
crate::algo_x_support::seed::cbrt_seed(&n_limbs, bits, &mut seed_limbs);
let mut y: u128 = (seed_limbs[0] as u128) | ((seed_limbs[1] as u128) << 64);
loop {
let (yy_hi, yy_lo) = mul_u128_to_u256(y, y);
let nq = div_384_by_256(n, [yy_lo, yy_hi]);
let (two_y, c0) = y.overflowing_add(y);
let (sum, c1) = two_y.overflowing_add(nq);
let carry = u128::from(c0) + u128::from(c1);
let y_next = floor_div3(carry, sum);
if y_next >= y {
return y;
}
y = y_next;
}
}
pub(crate) fn cbrt_raw_correctly_rounded(r: u128, scale: u32) -> u128 {
cbrt_raw_with_unsigned_mag(r, scale, crate::support::rounding::RoundingMode::HalfToEven)
}
pub(crate) fn cbrt_raw_with_unsigned_mag(
r: u128,
scale: u32,
mode: crate::support::rounding::RoundingMode,
) -> u128 {
cbrt_raw_with_signed(r, scale, false, mode)
}
pub(crate) fn cbrt_raw_with_signed(
r: u128,
scale: u32,
negative: bool,
mode: crate::support::rounding::RoundingMode,
) -> u128 {
use crate::support::rounding::RoundingMode;
if r == 0 {
return 0;
}
let n = mul_u128_by_256(r, pow10_256(2 * scale));
let q = icbrt_384(n);
let eight_n = shl3_384(n);
let two_q_plus_1 = 2 * q + 1;
let (sq_hi, sq_lo) = mul_u128_to_u256(two_q_plus_1, two_q_plus_1);
let cube = mul_u256_by_u128([sq_lo, sq_hi], two_q_plus_1);
let halfway_geq = ge_384(eight_n, cube);
let halfway_gt = gt_384(eight_n, cube);
let two_q = q + q;
let (tq_sq_hi, tq_sq_lo) = mul_u128_to_u256(two_q, two_q);
let eight_q_cubed = if q == 0 {
[0u128, 0, 0]
} else {
mul_u256_by_u128([tq_sq_lo, tq_sq_hi], two_q)
};
let residual_nonzero = gt_384(eight_n, eight_q_cubed);
let tie = halfway_geq && !halfway_gt;
let bump = match mode {
RoundingMode::HalfToEven => halfway_gt || (tie && (q & 1 == 1)),
RoundingMode::HalfAwayFromZero => halfway_geq,
RoundingMode::HalfTowardZero => halfway_gt,
RoundingMode::Trunc => false,
RoundingMode::Floor => negative && residual_nonzero,
RoundingMode::Ceiling => !negative && residual_nonzero,
};
if bump { q + 1 } else { q }
}
fn gt_384(a: [u128; 3], b: [u128; 3]) -> bool {
if a[2] != b[2] {
return a[2] > b[2];
}
if a[1] != b[1] {
return a[1] > b[1];
}
a[0] > b[0]
}
#[inline]
pub(crate) fn mul_div_pow10<const SCALE: u32>(a: i128, b: i128) -> Option<i128> {
mul_div_pow10_with::<SCALE>(a, b, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
pub(crate) fn mul_div_pow10_with<const SCALE: u32>(
a: i128,
b: i128,
mode: crate::support::rounding::RoundingMode,
) -> Option<i128> {
if SCALE == 0 {
return a.checked_mul(b);
}
if let Some(prod) = a.checked_mul(b) {
return Some(crate::support::rounding::apply_rounding(
prod,
crate::D::<crate::int::types::Int<2>, SCALE>::multiplier().as_i128(),
mode,
));
}
let ua = a.unsigned_abs();
let ub = b.unsigned_abs();
let exp = crate::D::<crate::int::types::Int<2>, SCALE>::multiplier().as_i128() as u128;
let (uprod, hi_overflow) = ua.overflowing_mul(ub);
if !hi_overflow {
let (q_floor, r) = if SCALE <= 19 {
let d = exp as u64;
let hi = (uprod >> 64) as u64;
let lo = uprod as u64;
if hi == 0 {
let q = lo / d;
let r = lo % d;
(q as u128, r as u128)
} else {
let q_hi = hi / d;
let r_hi = hi % d;
let cur = ((r_hi as u128) << 64) | (lo as u128);
let q_lo_u128 = cur / (d as u128);
let r = cur - q_lo_u128 * (d as u128);
let q = ((q_hi as u128) << 64) | (q_lo_u128 & u128::from(u64::MAX));
(q, r)
}
} else {
let q = uprod / exp;
(q, uprod - q * exp)
};
let neg = (a < 0) ^ (b < 0);
let q = round_mag_with_mode(q_floor, r, exp, mode, !neg);
return if neg {
if q <= i128::MAX as u128 {
Some(-(q as i128))
} else if q == (i128::MAX as u128) + 1 {
Some(i128::MIN)
} else {
None
}
} else if q <= i128::MAX as u128 {
Some(q as i128)
} else {
None
};
}
let (mhigh, mlow) = mul_u128_to_u256(ua, ub);
let (q_floor, r) = divmod_pow10_2word(mhigh, mlow, exp, SCALE as usize)?;
let neg = (a < 0) ^ (b < 0);
let q = round_mag_with_mode(q_floor, r, exp, mode, !neg);
if neg {
if q <= i128::MAX as u128 {
Some(-(q as i128))
} else if q == (i128::MAX as u128) + 1 {
Some(i128::MIN)
} else {
None
}
} else {
if q <= i128::MAX as u128 {
Some(q as i128)
} else {
None
}
}
}
#[inline]
pub(crate) fn div_pow10_div<const SCALE: u32>(a: i128, b: i128) -> Option<i128> {
div_pow10_div_with::<SCALE>(a, b, crate::support::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
pub(crate) fn div_pow10_div_with<const SCALE: u32>(
a: i128,
b: i128,
mode: crate::support::rounding::RoundingMode,
) -> Option<i128> {
if b == 0 {
return None;
}
a.checked_div(b)?;
if SCALE == 0 {
return Some(crate::support::rounding::apply_rounding(a, b, mode));
}
let mult = crate::D::<crate::int::types::Int<2>, SCALE>::multiplier().as_i128();
if let Some(num) = a.checked_mul(mult) {
return Some(crate::support::rounding::apply_rounding(num, b, mode));
}
let ua = a.unsigned_abs();
let umult = mult as u128;
if ua <= u128::MAX / umult {
let num = ua * umult;
let ub = b.unsigned_abs();
let q_floor = num / ub;
let r = num % ub;
let neg = (a < 0) ^ (b < 0);
let q = round_mag_with_mode(q_floor, r, ub, mode, !neg);
return if neg {
if q <= i128::MAX as u128 {
Some(-(q as i128))
} else if q == (i128::MAX as u128) + 1 {
Some(i128::MIN)
} else {
None
}
} else if q <= i128::MAX as u128 {
Some(q as i128)
} else {
None
};
}
let (mhigh, mlow) = mul_u128_to_u256(ua, umult);
let ub = b.unsigned_abs();
let (q_floor, r) = div_long_256_by_128_with_rem(mhigh, mlow, ub)?;
let neg = (a < 0) ^ (b < 0);
let q = round_mag_with_mode(q_floor, r, ub, mode, !neg);
if neg {
if q <= i128::MAX as u128 {
Some(-(q as i128))
} else if q == (i128::MAX as u128) + 1 {
Some(i128::MIN)
} else {
None
}
} else if q <= i128::MAX as u128 {
Some(q as i128)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mul_div_pow10_small_matches_naive() {
const SCALE: u32 = 12;
let a: i128 = 1_500_000_000;
let b: i128 = 2_300_000_000;
let expected = (a * b) / 1_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_mid_matches_naive() {
const SCALE: u32 = 12;
let a: i128 = 3_000_000_000_000_000;
let b: i128 = 4_700_000_000_000_000;
let expected = (a * b) / 1_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_bound_matches_naive() {
const SCALE: u32 = 12;
let a: i128 = 7_000_000_000_000_000_000;
let b: i128 = 4_700_000_000_000_000_000;
let expected = (a * b) / 1_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_wide_correctness() {
const SCALE: u32 = 12;
let a: i128 = 50_000_000_000_000_000_000_000;
let b: i128 = 30_000_000_000_000_000_000_000;
let expected: i128 = 1_500_000_000_000_000_000_000_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_overflows_to_none() {
const SCALE: u32 = 12;
let a: i128 = 10_i128.pow(26);
let b: i128 = 10_i128.pow(26);
assert_eq!(mul_div_pow10::<SCALE>(a, b), None);
}
#[test]
fn mul_div_pow10_negative_one_sided() {
const SCALE: u32 = 12;
let a: i128 = -50_000_000_000_000_000_000_000;
let b: i128 = 30_000_000_000_000_000_000_000;
let expected: i128 = -1_500_000_000_000_000_000_000_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_negative_both() {
const SCALE: u32 = 12;
let a: i128 = -50_000_000_000_000_000_000_000;
let b: i128 = -30_000_000_000_000_000_000_000;
let expected: i128 = 1_500_000_000_000_000_000_000_000_000_000_000_i128;
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(expected));
}
#[test]
fn mul_div_pow10_scale_zero() {
const SCALE: u32 = 0;
assert_eq!(mul_div_pow10::<SCALE>(7, 11), Some(77));
assert_eq!(mul_div_pow10::<SCALE>(-7, 11), Some(-77));
assert_eq!(mul_div_pow10::<SCALE>(i128::MAX, 1), Some(i128::MAX));
assert_eq!(mul_div_pow10::<SCALE>(i128::MAX, 2), None); }
#[test]
fn mul_div_pow10_scale_one() {
const SCALE: u32 = 1;
assert_eq!(mul_div_pow10::<SCALE>(25, 4), Some(10));
}
#[test]
fn mul_div_pow10_scale_eighteen() {
const SCALE: u32 = 18;
let a: i128 = 10_i128.pow(18);
let b: i128 = 10_i128.pow(18);
assert_eq!(mul_div_pow10::<SCALE>(a, b), Some(10_i128.pow(18)));
}
#[test]
fn div_pow10_div_small_matches_naive() {
if !crate::support::rounding::DEFAULT_IS_HALF_TO_EVEN {
return;
}
const SCALE: u32 = 12;
let a: i128 = 1_500_000_000;
let b: i128 = 7;
let expected = (a * 1_000_000_000_000_i128) / b;
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(expected));
}
#[test]
fn div_pow10_div_by_zero_is_none() {
const SCALE: u32 = 12;
assert_eq!(div_pow10_div::<SCALE>(123, 0), None);
}
#[test]
fn div_pow10_div_scale_zero() {
if !crate::support::rounding::DEFAULT_IS_HALF_TO_EVEN {
return;
}
const SCALE: u32 = 0;
assert_eq!(div_pow10_div::<SCALE>(15, 4), Some(4));
assert_eq!(div_pow10_div::<SCALE>(-15, 4), Some(-4));
assert_eq!(div_pow10_div::<SCALE>(16, 4), Some(4));
assert_eq!(div_pow10_div::<SCALE>(i128::MIN, -1), None);
}
#[test]
fn div_pow10_div_with_modes() {
use crate::support::rounding::RoundingMode::*;
const SCALE: u32 = 0;
assert_eq!(div_pow10_div_with::<SCALE>(15, 4, HalfToEven), Some(4));
assert_eq!(
div_pow10_div_with::<SCALE>(15, 4, HalfAwayFromZero),
Some(4)
);
assert_eq!(div_pow10_div_with::<SCALE>(15, 4, HalfTowardZero), Some(4));
assert_eq!(div_pow10_div_with::<SCALE>(15, 4, Trunc), Some(3));
assert_eq!(div_pow10_div_with::<SCALE>(15, 4, Floor), Some(3));
assert_eq!(div_pow10_div_with::<SCALE>(15, 4, Ceiling), Some(4));
assert_eq!(div_pow10_div_with::<SCALE>(-15, 4, Trunc), Some(-3));
assert_eq!(div_pow10_div_with::<SCALE>(-15, 4, Floor), Some(-4));
assert_eq!(div_pow10_div_with::<SCALE>(-15, 4, Ceiling), Some(-3));
}
#[test]
fn div_pow10_div_wide_correctness() {
const SCALE: u32 = 12;
let a: i128 = 10_i128.pow(22);
let b: i128 = 2;
let expected: i128 = 5 * 10_i128.pow(33);
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(expected));
}
#[test]
fn div_pow10_div_round_trip_small() {
const SCALE: u32 = 12;
let a: i128 = 6_000_000_000_000;
let b: i128 = 2_000_000_000_000;
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(3_000_000_000_000));
}
#[test]
fn div_pow10_div_negative_dividend() {
const SCALE: u32 = 12;
let a: i128 = -6_000_000_000_000;
let b: i128 = 2_000_000_000_000;
assert_eq!(div_pow10_div::<SCALE>(a, b), Some(-3_000_000_000_000));
}
#[test]
fn mul_div_round_trip_wide() {
const SCALE: u32 = 12;
let a: i128 = 50_000_000_000_000_000_000_000;
let b: i128 = 30_000_000_000_000_000_000_000;
let prod = mul_div_pow10::<SCALE>(a, b).expect("wide mul");
let recovered = div_pow10_div::<SCALE>(prod, b).expect("wide div");
assert_eq!(recovered, a);
}
#[test]
fn div_pow10_div_carry_propagation_under_directed_rounding() {
const SCALE: u32 = 12;
use crate::support::rounding::RoundingMode;
let a: i128 = 1_000_000_000_000; let b: i128 = 3_000_000_000_000;
let trunc = div_pow10_div_with::<SCALE>(a, b, RoundingMode::Trunc).unwrap();
let ceil = div_pow10_div_with::<SCALE>(a, b, RoundingMode::Ceiling).unwrap();
let floor = div_pow10_div_with::<SCALE>(a, b, RoundingMode::Floor).unwrap();
let ha = div_pow10_div_with::<SCALE>(a, b, RoundingMode::HalfAwayFromZero).unwrap();
let bits = [trunc, ceil, floor, ha];
let min = *bits.iter().min().unwrap();
let max = *bits.iter().max().unwrap();
assert!(max - min <= 1, "modes diverged > 1 LSB: {bits:?}");
assert!(ceil >= trunc);
let neg_floor = div_pow10_div_with::<SCALE>(-a, b, RoundingMode::Floor).unwrap();
let neg_trunc = div_pow10_div_with::<SCALE>(-a, b, RoundingMode::Trunc).unwrap();
assert!(neg_floor <= neg_trunc);
}
#[test]
fn isqrt_256_zero_input() {
assert_eq!(isqrt_256(0, 0), 0);
}
#[test]
fn sqrt_raw_correctly_rounded_zero_input() {
for s in 0..=12 {
assert_eq!(sqrt_raw_correctly_rounded(0, s), 0);
}
}
#[test]
fn cbrt_raw_correctly_rounded_zero_input() {
for s in 0..=12 {
assert_eq!(cbrt_raw_correctly_rounded(0, s), 0);
}
}
#[test]
fn sqrt_raw_correctly_rounded_post_condition_holds() {
for r in [
7u128,
1_500_000_000_000u128,
10u128.pow(20),
(i128::MAX as u128) / 7,
] {
let q = sqrt_raw_correctly_rounded(r, 12);
let (n_hi, n_lo) = mul_u128_to_u256(r, POW10_U128[12]);
let q_floor = isqrt_256(n_hi, n_lo);
assert!(
q == q_floor || q == q_floor + 1,
"sqrt({r}, 12): q={q}, floor={q_floor}",
);
let (qq_hi, qq_lo) = mul_u128_to_u256(q_floor, q_floor);
let (diff_hi, diff_lo) = if n_lo >= qq_lo {
(n_hi - qq_hi, n_lo - qq_lo)
} else {
(n_hi - qq_hi - 1, n_lo.wrapping_sub(qq_lo))
};
let should_round_up = diff_hi != 0 || diff_lo > q_floor;
let expected = if should_round_up {
q_floor + 1
} else {
q_floor
};
assert_eq!(q, expected, "sqrt({r}, 12): wrong round-up decision");
}
}
#[test]
fn cbrt_raw_high_limb_radicand_post_condition_holds() {
for r in [
8u128,
27u128,
1_000_000u128,
10u128.pow(20),
(i128::MAX as u128) / 7,
] {
let q = cbrt_raw_correctly_rounded(r, 12);
let n = mul_u128_by_256(r, pow10_256(24));
let q_floor = icbrt_384(n);
assert!(
q == q_floor || q == q_floor + 1,
"cbrt({r}, 12): q={q}, floor={q_floor}",
);
let eight_n = shl3_384(n);
let two_q_plus_1 = 2 * q_floor + 1;
let (sq_hi, sq_lo) = mul_u128_to_u256(two_q_plus_1, two_q_plus_1);
let cube = mul_u256_by_u128([sq_lo, sq_hi], two_q_plus_1);
let expected = if ge_384(eight_n, cube) {
q_floor + 1
} else {
q_floor
};
assert_eq!(q, expected, "cbrt({r}, 12): wrong round-up decision");
}
}
#[test]
fn cbrt_raw_correctly_rounded_post_condition_holds() {
for (r, scale) in [
(8u128, 0), (27u128, 0), (9u128, 0), (64u128, 0),
(65u128, 0),
(10u128.pow(12), 6), ] {
let q = cbrt_raw_correctly_rounded(r, scale);
let n = mul_u128_by_256(r, pow10_256(2 * scale));
let q_floor = icbrt_384(n);
assert!(
q == q_floor || q == q_floor + 1,
"cbrt({r}, {scale}): q={q}, floor={q_floor} — expected q ∈ {{floor, floor+1}}",
);
let eight_n = shl3_384(n);
let two_q_plus_1 = 2 * q_floor + 1;
let (sq_hi, sq_lo) = mul_u128_to_u256(two_q_plus_1, two_q_plus_1);
let cube = mul_u256_by_u128([sq_lo, sq_hi], two_q_plus_1);
let should_round_up = ge_384(eight_n, cube);
let expected = if should_round_up {
q_floor + 1
} else {
q_floor
};
assert_eq!(
q, expected,
"cbrt({r}, {scale}): round-up decision mismatched",
);
}
}
fn sqrt_raw_reference(
r: u128,
scale: u32,
mode: crate::support::rounding::RoundingMode,
) -> u128 {
use crate::support::rounding::RoundingMode;
if r == 0 {
return 0;
}
let (hi, lo) = mul_u128_to_u256(r, POW10_U128[scale as usize]);
let q = isqrt_256(hi, lo);
let (q_sq_hi, q_sq_lo) = mul_u128_to_u256(q, q);
let (diff_hi, diff_lo) = if lo >= q_sq_lo {
(hi - q_sq_hi, lo - q_sq_lo)
} else {
(hi - q_sq_hi - 1, lo.wrapping_sub(q_sq_lo))
};
let diff_nonzero = diff_hi != 0 || diff_lo != 0;
let halfway_round_up = diff_hi != 0 || diff_lo > q;
let bump = match mode {
RoundingMode::HalfToEven
| RoundingMode::HalfAwayFromZero
| RoundingMode::HalfTowardZero => halfway_round_up,
RoundingMode::Trunc | RoundingMode::Floor => false,
RoundingMode::Ceiling => diff_nonzero,
};
if bump { q + 1 } else { q }
}
struct SplitMix64(u64);
impl SplitMix64 {
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn next_u128(&mut self) -> u128 {
((self.next() as u128) << 64) | (self.next() as u128)
}
}
fn all_modes() -> [crate::support::rounding::RoundingMode; 6] {
use crate::support::rounding::RoundingMode::*;
[
HalfToEven,
HalfAwayFromZero,
HalfTowardZero,
Trunc,
Floor,
Ceiling,
]
}
#[test]
fn sqrt_fast_path_matches_reference() {
const ITERS: usize = 200_000;
let scales: &[u32] = &[0, 5, 10, 14, 18, 19, 23, 28, 33, 38];
for &scale in scales {
for mode in all_modes() {
let mut rng = SplitMix64(0xC0FFEE_u64.wrapping_add(scale as u64));
let pow = POW10_U128[scale as usize];
let cap = u128::MAX / pow;
for _ in 0..ITERS {
let raw = match rng.next() % 3 {
0 => rng.next_u128() % cap.max(1),
1 => {
let bias = rng.next_u128() % (cap.max(1));
cap.saturating_sub(bias).saturating_add(rng.next() as u128)
}
_ => rng.next_u128() & (i128::MAX as u128), };
let got = sqrt_raw_with(raw, scale, mode);
let expected = sqrt_raw_reference(raw, scale, mode);
assert_eq!(
got, expected,
"sqrt mismatch: raw={raw}, scale={scale}, mode={mode:?}",
);
}
}
}
}
fn div_pow10_div_reference<const SCALE: u32>(
a: i128,
b: i128,
mode: crate::support::rounding::RoundingMode,
) -> Option<i128> {
if b == 0 {
return None;
}
a.checked_div(b)?;
if SCALE == 0 {
return Some(crate::support::rounding::apply_rounding(a, b, mode));
}
let mult = crate::D::<crate::int::types::Int<2>, SCALE>::multiplier().as_i128();
let ua = a.unsigned_abs();
let umult = mult as u128;
let (mhigh, mlow) = mul_u128_to_u256(ua, umult);
let ub = b.unsigned_abs();
let (q_floor, r) = div_long_256_by_128_with_rem(mhigh, mlow, ub)?;
let neg = (a < 0) ^ (b < 0);
let q = round_mag_with_mode(q_floor, r, ub, mode, !neg);
if neg {
if q <= i128::MAX as u128 {
Some(-(q as i128))
} else if q == (i128::MAX as u128) + 1 {
Some(i128::MIN)
} else {
None
}
} else if q <= i128::MAX as u128 {
Some(q as i128)
} else {
None
}
}
#[test]
fn div_pow10_div_fast_path_matches_reference() {
const ITERS: usize = 100_000;
macro_rules! sweep {
($scale:literal) => {{
const SCALE: u32 = $scale;
for mode in all_modes() {
let mut rng = SplitMix64(0xDEADBEEF_u64 + SCALE as u64);
for _ in 0..ITERS {
let a = rng.next_u128() as i128;
let mut b: i128 = rng.next_u128() as i128;
if b == 0 {
b = 1;
}
let got = div_pow10_div_with::<SCALE>(a, b, mode);
let expected = div_pow10_div_reference::<SCALE>(a, b, mode);
assert_eq!(
got, expected,
"div mismatch: a={a}, b={b}, scale={SCALE}, mode={mode:?}",
);
}
}
}};
}
sweep!(0);
sweep!(5);
sweep!(10);
sweep!(14);
sweep!(18);
sweep!(19);
sweep!(23);
sweep!(28);
sweep!(33);
sweep!(38);
}
#[cfg(any(feature = "d76", feature = "wide"))]
fn round_div_reference_int256(n: crate::int::types::Int<4>, w: u32) -> crate::int::types::Int<4> {
use crate::int::types::Int;
let d_u128 = POW10_U128[w as usize];
let d: Int<4> = Int::from_u128(d_u128);
let zero: Int<4> = Int::from_u128(0u128);
let one: Int<4> = Int::from_u128(1u128);
let (q, r) = n.div_rem(d);
if r == zero {
return q;
}
let ar = if r < zero { -r } else { r };
let comp = d - ar;
let cmp_r = ar.cmp(&comp);
let q_is_odd = q.bit(0);
let result_positive = n >= zero;
let bump = crate::support::rounding::should_bump(
crate::support::rounding::RoundingMode::HalfToEven,
cmp_r,
q_is_odd,
result_positive,
);
if bump {
if result_positive { q + one } else { q - one }
} else {
q
}
}
#[cfg(any(feature = "d76", feature = "wide"))]
fn round_div_reference_int256_with(
n: crate::int::types::Int<4>,
w: u32,
mode: crate::support::rounding::RoundingMode,
) -> crate::int::types::Int<4> {
use crate::int::types::Int;
let d_u128 = POW10_U128[w as usize];
let d: Int<4> = Int::from_u128(d_u128);
let zero: Int<4> = Int::from_u128(0u128);
let one: Int<4> = Int::from_u128(1u128);
let (q, r) = n.div_rem(d);
if r == zero {
return q;
}
let ar = if r < zero { -r } else { r };
let comp = d - ar;
let cmp_r = ar.cmp(&comp);
let q_is_odd = q.bit(0);
let result_positive = n >= zero;
let bump = crate::support::rounding::should_bump(mode, cmp_r, q_is_odd, result_positive);
if bump {
if result_positive { q + one } else { q - one }
} else {
q
}
}
#[cfg(any(feature = "d76", feature = "wide"))]
#[test]
fn round_div_audit_mg_matches_div_rem_int256() {
use crate::int::types::Int;
const ITERS: usize = 10_000;
for w in 1u32..=38 {
let mut rng = SplitMix64(0xA17D17_u64.wrapping_add(w as u64));
for _ in 0..ITERS {
let regime = rng.next() % 4;
let mag_high = if regime >= 2 {
rng.next_u128() & (i128::MAX as u128)
} else {
0
};
let mag_low = rng.next_u128();
let pos: Int<4> = {
let lo: Int<4> = Int::from_u128(mag_low);
let hi: Int<4> = Int::from_u128(mag_high);
(hi << 128_u32) + lo
};
let n: Int<4> = if regime % 2 == 1 { -pos } else { pos };
let got =
crate::algos::support::mg_divide::div_wide_pow10::<Int<4>>(n, w, crate::support::rounding::RoundingMode::HalfToEven);
let expected = round_div_reference_int256(n, w);
assert_eq!(got, expected, "round_div MG audit mismatch: w={w}, n={n:?}",);
}
}
}
#[cfg(any(feature = "d76", feature = "wide"))]
#[test]
fn round_div_audit_mg_all_modes_int256() {
use crate::int::types::Int;
const ITERS: usize = 2_000;
let scales: &[u32] = &[1, 5, 10, 19, 28, 38];
for &w in scales {
for mode in all_modes() {
let mut rng = SplitMix64(0xCAFE_u64.wrapping_add(w as u64 ^ mode as u64));
for _ in 0..ITERS {
let regime = rng.next() % 4;
let mag_high = if regime >= 2 {
rng.next_u128() & (i128::MAX as u128)
} else {
0
};
let mag_low = rng.next_u128();
let pos: Int<4> = {
let lo: Int<4> = Int::from_u128(mag_low);
let hi: Int<4> = Int::from_u128(mag_high);
(hi << 128_u32) + lo
};
let n: Int<4> = if regime % 2 == 1 { -pos } else { pos };
let got = crate::algos::support::mg_divide::div_wide_pow10::<Int<4>>(n, w, mode);
let expected = round_div_reference_int256_with(n, w, mode);
assert_eq!(
got, expected,
"round_div MG all-modes mismatch: w={w}, mode={mode:?}",
);
}
}
}
}
#[cfg(any(feature = "d307", feature = "wide"))]
#[test]
fn round_div_audit_mg_matches_div_rem_int1024() {
use crate::int::types::Int;
let zero: Int<16> = Int::from_u128(0u128);
let one: Int<16> = Int::from_u128(1u128);
const ITERS: usize = 5_000;
for w in 1u32..=38 {
let mut rng = SplitMix64(0xB02ED2_u64.wrapping_add(w as u64));
let d: Int<16> = Int::from_u128(POW10_U128[w as usize]);
for _ in 0..ITERS {
let limbs = (rng.next() % 7) as usize;
let mut n: Int<16> = zero;
for k in 0..limbs {
let chunk: Int<16> = Int::from_u128(rng.next_u128());
n = n + (chunk << ((k * 128) as u32));
}
if rng.next() & 1 == 1 {
n = -n;
}
let got =
crate::algos::support::mg_divide::div_wide_pow10::<Int<16>>(n, w, crate::support::rounding::RoundingMode::HalfToEven);
let (q, r) = n.div_rem(d);
let expected = if r == zero {
q
} else {
let ar = if r < zero { -r } else { r };
let comp = d - ar;
let cmp_r = ar.cmp(&comp);
let q_is_odd = q.bit(0);
let result_positive = n >= zero;
let bump = crate::support::rounding::should_bump(
crate::support::rounding::RoundingMode::HalfToEven,
cmp_r,
q_is_odd,
result_positive,
);
if bump {
if result_positive { q + one } else { q - one }
} else {
q
}
};
assert_eq!(got, expected, "round_div MG audit (Int<16>) mismatch: w={w}",);
}
}
}
#[cfg(any(feature = "d76", feature = "wide"))]
fn pow10_int256(w: u32) -> crate::int::types::Int<4> {
use crate::int::types::Int;
let mut d: Int<4> = Int::from_u128(1u128);
let mut remaining = w;
while remaining > 0 {
let chunk = remaining.min(38);
let factor: Int<4> = Int::from_u128(POW10_U128[chunk as usize]);
d = d * factor;
remaining -= chunk;
}
d
}
#[cfg(any(feature = "d307", feature = "wide"))]
fn pow10_int1024(w: u32) -> crate::int::types::Int<16> {
use crate::int::types::Int;
let mut d: Int<16> = Int::from_u128(1u128);
let mut remaining = w;
while remaining > 0 {
let chunk = remaining.min(38);
let factor: Int<16> = Int::from_u128(POW10_U128[chunk as usize]);
d = d * factor;
remaining -= chunk;
}
d
}
#[cfg(any(feature = "d76", feature = "wide"))]
fn round_div_chain_reference_int256(
n: crate::int::types::Int<4>,
w: u32,
mode: crate::support::rounding::RoundingMode,
) -> crate::int::types::Int<4> {
use crate::int::types::Int;
let d = pow10_int256(w);
let zero: Int<4> = Int::from_u128(0u128);
let one: Int<4> = Int::from_u128(1u128);
let (q, r) = n.div_rem(d);
if r == zero {
return q;
}
let ar = if r < zero { -r } else { r };
let comp = d - ar;
let cmp_r = ar.cmp(&comp);
let q_is_odd = q.bit(0);
let result_positive = n >= zero;
let bump = crate::support::rounding::should_bump(mode, cmp_r, q_is_odd, result_positive);
if bump {
if result_positive { q + one } else { q - one }
} else {
q
}
}
#[cfg(any(feature = "d307", feature = "wide"))]
fn round_div_chain_reference_int1024(
n: crate::int::types::Int<16>,
w: u32,
mode: crate::support::rounding::RoundingMode,
) -> crate::int::types::Int<16> {
use crate::int::types::Int;
let d = pow10_int1024(w);
let zero: Int<16> = Int::from_u128(0u128);
let one: Int<16> = Int::from_u128(1u128);
let (q, r) = n.div_rem(d);
if r == zero {
return q;
}
let ar = if r < zero { -r } else { r };
let comp = d - ar;
let cmp_r = ar.cmp(&comp);
let q_is_odd = q.bit(0);
let result_positive = n >= zero;
let bump = crate::support::rounding::should_bump(mode, cmp_r, q_is_odd, result_positive);
if bump {
if result_positive { q + one } else { q - one }
} else {
q
}
}
#[cfg(any(feature = "d76", feature = "wide"))]
#[test]
fn round_div_chain_audit_int256_w39_76_all_modes() {
use crate::int::types::Int;
const ITERS_HTE: usize = 5_000;
const ITERS_OTHER: usize = 1_000;
for w in 39u32..=76 {
for mode in all_modes() {
let iters = if matches!(mode, crate::support::rounding::RoundingMode::HalfToEven) {
ITERS_HTE
} else {
ITERS_OTHER
};
let mut rng = SplitMix64(0xC4A1_u64.wrapping_add((w as u64) << 8 ^ (mode as u64)));
for _ in 0..iters {
let regime = rng.next() % 4;
let mag_high = if regime >= 2 {
rng.next_u128() & (i128::MAX as u128)
} else {
0
};
let mag_low = rng.next_u128();
let pos: Int<4> = {
let lo: Int<4> = Int::from_u128(mag_low);
let hi: Int<4> = Int::from_u128(mag_high);
(hi << 128_u32) + lo
};
let n: Int<4> = if regime % 2 == 1 { -pos } else { pos };
let got = crate::algos::support::mg_divide::div_wide_pow10_chain::<Int<4>>(n, w, mode);
let expected = round_div_chain_reference_int256(n, w, mode);
assert_eq!(
got, expected,
"chain MG audit (Int<4>) mismatch: w={w}, mode={mode:?}, n={n:?}",
);
}
}
}
}
#[cfg(any(feature = "d307", feature = "wide"))]
#[test]
fn round_div_chain_audit_int1024_w39_100() {
use crate::int::types::Int;
let zero: Int<16> = Int::from_u128(0u128);
const ITERS_HTE: usize = 3_000;
for w in 39u32..=100 {
let mut rng = SplitMix64(0x5C5C_u64.wrapping_add(w as u64));
for _ in 0..ITERS_HTE {
let limbs = (rng.next() % 7) as usize;
let mut n: Int<16> = zero;
for k in 0..limbs {
let chunk: Int<16> = Int::from_u128(rng.next_u128());
n = n + (chunk << ((k * 128) as u32));
}
if rng.next() & 1 == 1 {
n = -n;
}
let got =
crate::algos::support::mg_divide::div_wide_pow10_chain::<Int<16>>(n, w, crate::support::rounding::RoundingMode::HalfToEven);
let expected = round_div_chain_reference_int1024(
n,
w,
crate::support::rounding::RoundingMode::HalfToEven,
);
assert_eq!(got, expected, "chain MG audit (Int<16> HTE) mismatch: w={w}",);
}
}
}
#[cfg(any(feature = "d307", feature = "wide"))]
#[test]
fn round_div_chain_audit_int1024_all_modes_sample_w() {
use crate::int::types::Int;
let zero: Int<16> = Int::from_u128(0u128);
const ITERS: usize = 1_000;
let ws: &[u32] = &[39, 50, 57, 76, 77, 88, 100];
for &w in ws {
for mode in all_modes() {
let mut rng = SplitMix64(0x9E15_u64.wrapping_add((w as u64) << 4 ^ mode as u64));
for _ in 0..ITERS {
let limbs = (rng.next() % 7) as usize;
let mut n: Int<16> = zero;
for k in 0..limbs {
let chunk: Int<16> = Int::from_u128(rng.next_u128());
n = n + (chunk << ((k * 128) as u32));
}
if rng.next() & 1 == 1 {
n = -n;
}
let got = crate::algos::support::mg_divide::div_wide_pow10_chain::<Int<16>>(n, w, mode);
let expected = round_div_chain_reference_int1024(n, w, mode);
assert_eq!(
got, expected,
"chain MG audit (Int<16> all modes) mismatch: w={w}, mode={mode:?}",
);
}
}
}
}
#[cfg(any(feature = "d307", feature = "wide"))]
#[test]
fn round_div_chain_audit_int1024_constructed_half_ties() {
use crate::int::types::Int;
let two: Int<16> = Int::from_u128(2u128);
let ws: &[u32] = &[39, 50, 57, 76, 77, 100];
for &w in ws {
let pow_w = pow10_int1024(w);
let half = pow_w / two;
let qs: [Int<16>; 3] = [
Int::<16>::from_u128(0u128),
Int::<16>::from_u128(1u128),
Int::<16>::from_u128(7u128) << 200_u32,
];
let deltas: [Int<16>; 3] = [
Int::<16>::from_u128(0u128),
-Int::<16>::from_u128(1u128),
Int::<16>::from_u128(1u128),
];
for q in qs {
for delta in deltas {
for &sign_neg in &[false, true] {
let pos_n = q * pow_w + half + delta;
let n = if sign_neg { -pos_n } else { pos_n };
for mode in all_modes() {
let got = crate::algos::support::mg_divide::div_wide_pow10_chain::<Int<16>>(n, w, mode);
let expected = round_div_chain_reference_int1024(n, w, mode);
assert_eq!(
got, expected,
"chain MG half-tie mismatch: w={w}, mode={mode:?}, n={n:?}",
);
}
}
}
}
}
}
#[cfg(any(feature = "d1232", feature = "xx-wide"))]
fn pow10_int16384(w: u32) -> crate::int::types::Int<256> {
use crate::int::types::Int;
let mut d: Int<256> = Int::from_u128(1u128);
let mut remaining = w;
while remaining > 0 {
let chunk = remaining.min(38);
let factor: Int<256> = Int::from_u128(POW10_U128[chunk as usize]);
d = d * factor;
remaining -= chunk;
}
d
}
#[cfg(any(feature = "d1232", feature = "xx-wide"))]
#[test]
fn round_div_chain_int16384_above_8192_bits_not_truncated() {
use crate::int::types::Int;
let zero: Int<256> = Int::from_u128(0u128);
let one: Int<256> = Int::from_u128(1u128);
let high_bit_positions: &[u32] = &[8200, 10000, 12345, 16000];
let scales: &[u32] = &[620, 700, 1024];
for &bit in high_bit_positions {
let mut n: Int<256> = one << bit;
n = n
+ (Int::<256>::from_u128(0xdead_beef_cafe_f00d_u128)
<< 64_u32);
n = n + Int::<256>::from_u128(0x1234_5678_9abc_def0_u128);
for &w in scales {
for &sign_neg in &[false, true] {
let nn = if sign_neg { -n } else { n };
for mode in all_modes() {
let got = crate::algos::support::mg_divide::div_wide_pow10_chain::<Int<256>>(nn, w, mode);
let d = pow10_int16384(w);
let (q, r) = nn.div_rem(d);
let expected = if r == zero {
q
} else {
let ar = if r < zero { -r } else { r };
let comp = d - ar;
let cmp_r = ar.cmp(&comp);
let q_is_odd = q.bit(0);
let result_positive = nn >= zero;
if crate::support::rounding::should_bump(
mode,
cmp_r,
q_is_odd,
result_positive,
) {
if result_positive { q + one } else { q - one }
} else {
q
}
};
assert_eq!(
got, expected,
"Int<256> chain divide truncated above 8192 bits: \
bit={bit}, w={w}, mode={mode:?}, neg={sign_neg}",
);
}
}
}
}
}
#[test]
fn mg_magics_match_paper_formula() {
for k in 1..=38usize {
let d = POW10_U128[k];
let (recip, s) = MG_EXP_MAGICS[k];
assert_eq!(s, d.leading_zeros(), "shift for 10^{k}");
let d_norm = d << s;
let (q_hi, q_lo) = mul_u128_to_u256(recip, d_norm);
let prod_hi = q_hi + d_norm;
assert!(
prod_hi <= 1u128 << 127 || q_hi <= d_norm,
"reciprocal lower bound 10^{k}"
);
let exp = d;
for &n_low in &[0u128, 1, exp - 1, exp, exp + 7, u128::MAX] {
let (q, r) =
divmod_pow10_2word(0, n_low, exp, k).expect("quotient fits when n_high == 0");
assert_eq!(q, n_low / exp, "kernel quotient 10^{k}, n_low={n_low}");
assert_eq!(r, n_low % exp, "kernel remainder 10^{k}, n_low={n_low}");
}
}
}
}