use crate::core_type::D38;
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 MG_EXP_MAGICS: [(u128, u32); 39] = [
(0, 0),
(0x99999999999999999999999999999999, 124),
(0x47ae147ae147ae147ae147ae147ae147, 121),
(0x0624dd2f1a9fbe76c8b4395810624dd2, 118),
(0xa36e2eb1c432ca57a786c226809d4951, 114),
(0x4f8b588e368f08461f9f01b866e43aa7, 111),
(0x0c6f7a0b5ed8d36b4c7f34938583621f, 108),
(0xad7f29abcaf485787a6520ec08d23699, 104),
(0x5798ee2308c39df9fb841a566d74f87a, 101),
(0x12e0be826d694b2e62d01511f12a6061, 98),
(0xb7cdfd9d7bdbab7d6ae6881cb5109a36, 94),
(0x5fd7fe17964955fdef1ed34a2a73ae91, 91),
(0x19799812dea11197f27f0f6e885c8ba7, 88),
(0xc25c268497681c2650cb4be40d60df73, 84),
(0x6849b86a12b9b01ea70909833de71928, 81),
(0x203af9ee756159b21f3a6e0297ec1420, 78),
(0xcd2b297d889bc2b6985d7cd0f3135367, 74),
(0x70ef54646d496892137dfd73f5a90f85, 71),
(0x2725dd1d243aba0e75fe645cc4873f9e, 68),
(0xd83c94fb6d2ac34a5663d3c7a0d865ca, 64),
(0x79ca10c9242235d511e976394d79eb08, 61),
(0x2e3b40a0e9b4f7dda7edf82dd794bc06, 58),
(0xe392010175ee5962a6498d1625bac670, 54),
(0x82db34012b25144eeb6e0a781e2f0527, 51),
(0x357c299a88ea76a58924d52ce4f26a85, 48),
(0xef2d0f5da7dd8aa27507bb7b07ea4409, 44),
(0x8c240c4aecb13bb52a6c95fc0655033a, 41),
(0x3ce9a36f23c0fc90eebd44c99eaa68fb, 38),
(0xfb0f6be50601941b17953adc3110a7f8, 34),
(0x95a5efea6b34767c12ddc8b027408660, 31),
(0x4484bfeebc29f863424b06f3529a051a, 28),
(0x039d66589687f9e901d59f290ee19dae, 25),
(0x9f623d5a8a732974cfbc31db4b0295e4, 21),
(0x4c4e977ba1f5bac3d9635b15d59bab1c, 18),
(0x09d8792fb4c495697ab5e277de16227d, 15),
(0xa95a5b7f87a0ef0f2abc9d8c9689d0c8, 11),
(0x54484932d2e725a5bbca17a3aba173d3, 8),
(0x1039d428a8b8eaeafca1ac82efb45ca9, 5),
(0xb38fb9daa78e44ab2dcf7a6b19209442, 1),
];
#[inline]
pub(crate) const fn mul2(a: u128, b: u128) -> (u128, u128) {
let (ahigh, alow) = (a >> 64, a & u64::MAX as u128);
let (bhigh, blow) = (b >> 64, b & u64::MAX as u128);
let (mid, carry1) = (alow * bhigh).overflowing_add(ahigh * blow);
let (mlow, carry2) = (alow * blow).overflowing_add(mid << 64);
let mhigh = ahigh * bhigh + (mid >> 64) + ((carry1 as u128) << 64) + carry2 as u128;
(mhigh, mlow)
}
#[inline]
fn div_exp_fast_2word_with_rem(
n_high: u128,
n_low: u128,
exp: u128,
scale_idx: usize,
) -> Option<(u128, u128)> {
if n_high >= exp {
return None;
}
let (magic, zeros) = MG_EXP_MAGICS[scale_idx];
let z_high = (n_high << zeros) | (n_low >> (128 - zeros));
let z_low = n_low << zeros;
let (m1_high, _) = mul2(z_low, magic);
let (m2_high, m2_low) = mul2(z_high, magic);
let (m_low, carry) = m2_low.overflowing_add(m1_high);
let m_high = m2_high + u128::from(carry);
let (_, carry) = m_low.overflowing_add(z_low);
let q = m_high + z_high + u128::from(carry);
let (pp_high, pp_low) = mul2(q, exp);
let (r_low, borrow) = n_low.overflowing_sub(pp_low);
debug_assert!(n_high == pp_high + u128::from(borrow));
if r_low < exp {
Some((q, r_low))
} else {
Some((q + 1, r_low - exp))
}
}
#[cfg(any(
feature = "d76",
feature = "d153",
feature = "d307",
feature = "wide",
feature = "x-wide"
))]
#[inline]
pub(crate) fn div_wide_pow10_with<W: crate::wide_int::WideInt>(
n: W,
scale: u32,
mode: crate::rounding::RoundingMode,
) -> W {
debug_assert!((1..=38).contains(&scale));
let (mut mag, neg) = n.to_mag_sign();
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) = div_exp_fast_2word_with_rem(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::rounding::should_bump(mode, cmp_r, q_is_odd, !neg) {
let mut carry: u128 = 1;
for limb in &mut mag {
let (s, c) = limb.overflowing_add(carry);
*limb = s;
if !c {
carry = 0;
break;
}
carry = 1;
}
let _ = carry;
}
}
W::from_mag_sign(&mag, neg)
}
#[inline]
fn round_mag_with_mode(
q: u128,
r: u128,
m: u128,
mode: crate::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::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 mut q: u128 = 1u128 << bits.div_ceil(2);
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 {
if r == 0 {
return 0;
}
let (hi, lo) = mul2(r, POW10_U128[scale as usize]);
let q = isqrt_256(hi, lo);
let (q_sq_hi, q_sq_lo) = mul2(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))
};
if diff_hi != 0 || diff_lo > q {
q + 1
} else {
q
}
}
fn pow10_256(exp: u32) -> [u128; 2] {
if exp <= 38 {
[10u128.pow(exp), 0]
} else {
let (hi, lo) = mul2(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) = mul2(a, m[0]);
let (p1_hi, p1_lo) = mul2(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) = mul2(s[0], b);
let (p1_hi, p1_lo) = mul2(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 mut y: u128 = 1u128 << (bits.div_ceil(3).min(127));
loop {
let (yy_hi, yy_lo) = mul2(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 {
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) = mul2(two_q_plus_1, two_q_plus_1);
let cube = mul_u256_by_u128([sq_lo, sq_hi], two_q_plus_1);
if ge_384(eight_n, cube) {
q + 1
} else {
q
}
}
#[inline]
pub(crate) fn mul_div_pow10<const SCALE: u32>(a: i128, b: i128) -> Option<i128> {
mul_div_pow10_with::<SCALE>(a, b, crate::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
pub(crate) fn mul_div_pow10_with<const SCALE: u32>(
a: i128,
b: i128,
mode: crate::rounding::RoundingMode,
) -> Option<i128> {
if SCALE == 0 {
return a.checked_mul(b);
}
if let Some(prod) = a.checked_mul(b) {
return Some(crate::rounding::apply_rounding(
prod,
D38::<SCALE>::multiplier(),
mode,
));
}
let ua = a.unsigned_abs();
let ub = b.unsigned_abs();
let (mhigh, mlow) = mul2(ua, ub);
let exp = D38::<SCALE>::multiplier() as u128;
let (q_floor, r) = div_exp_fast_2word_with_rem(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::rounding::DEFAULT_ROUNDING_MODE)
}
#[inline]
pub(crate) fn div_pow10_div_with<const SCALE: u32>(
a: i128,
b: i128,
mode: crate::rounding::RoundingMode,
) -> Option<i128> {
if b == 0 {
return None;
}
a.checked_div(b)?;
if SCALE == 0 {
return Some(crate::rounding::apply_rounding(a, b, mode));
}
let mult = D38::<SCALE>::multiplier();
if let Some(num) = a.checked_mul(mult) {
return Some(crate::rounding::apply_rounding(num, b, mode));
}
let ua = a.unsigned_abs();
let umult = mult as u128;
let (mhigh, mlow) = mul2(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::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::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::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::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) = mul2(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) = mul2(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) = mul2(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) = mul2(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",
);
}
}
}