use crate::int::types::traits::BigInt;
use crate::int::types::compute_limbs::{ComputeLimbs, Limbs};
use crate::int::types::Int;
use crate::support::rounding::RoundingMode;
#[inline]
fn narrow_mag_to_int<const N: usize>(mag: &[u128], neg: bool, msg: &str) -> Int<N> {
let u128_limbs = N.div_ceil(2);
let mut overflow = mag.iter().skip(u128_limbs).any(|&l| l != 0);
if (N & 1) == 1 {
if let Some(&top) = mag.get(u128_limbs - 1) {
overflow |= (top >> 64) != 0;
}
}
if !overflow {
let limit = if neg {
Int::<N>::MIN.unsigned_abs()
} else {
Int::<N>::MAX.unsigned_abs()
};
let limit_limbs = *limit.as_limbs();
let mut got = [0u64; N];
let pairs = (N / 2).min(u128_limbs).min(mag.len());
let mut i = 0;
while i < pairs {
got[2 * i] = mag[i] as u64;
got[2 * i + 1] = (mag[i] >> 64) as u64;
i += 1;
}
if (N & 1) == 1 && i < u128_limbs && i < mag.len() {
got[2 * i] = mag[i] as u64;
}
let mut k = N;
while k > 0 {
k -= 1;
if got[k] != limit_limbs[k] {
overflow = got[k] > limit_limbs[k];
break;
}
}
}
if overflow {
panic!("{msg}");
}
Int::<N>::from_mag_sign_u128(mag, neg)
}
#[inline]
pub(crate) fn mul_widen_divide<const N: usize, const SCALE: u32>(
a: Int<N>,
b: Int<N>,
mode: RoundingMode,
) -> Int<N>
where
Limbs<N>: ComputeLimbs,
{
let neg = a.is_negative() != b.is_negative();
let lz_a = a.unsigned_abs().leading_zeros();
let lz_b = b.unsigned_abs().leading_zeros();
if lz_a + lz_b > <Int<N>>::BITS {
let prod: Int<N> = a.wrapping_mul(b);
if SCALE == 0 {
return prod;
}
let u128_limbs = N.div_ceil(2);
let mut mag = [0u128; N];
let _ = prod.mag_into_u128(&mut mag[..u128_limbs]);
crate::algos::support::rescale::dispatch_mag(
&mut mag[..u128_limbs],
SCALE,
neg,
mode,
<Int<N>>::BITS,
);
return Int::<N>::from_mag_sign_u128(&mag[..u128_limbs], neg);
}
let a_mag = *a.unsigned_abs().as_limbs();
let b_mag = *b.unsigned_abs().as_limbs();
let mut prod_buf = Limbs::<N>::double_buffered_u64();
crate::int::policy::mul::dispatch::<N>(&a_mag, &b_mag, prod_buf.as_mut());
let prod = prod_buf.as_ref();
let mut mag = [0u128; N];
for i in 0..N {
let lo = prod[2 * i] as u128;
let hi = *prod.get(2 * i + 1).unwrap_or(&0) as u128;
mag[i] = lo | (hi << 64);
}
if SCALE == 0 {
return narrow_mag_to_int::<N>(&mag, neg, "attempt to multiply with overflow");
}
let mut sig = mag.len();
while sig > 1 && mag[sig - 1] == 0 {
sig -= 1;
}
let sig_bits = (sig as u32).saturating_mul(128).min((2 * N as u32) * 64);
crate::algos::support::rescale::dispatch_mag(&mut mag[..sig], SCALE, neg, mode, sig_bits);
narrow_mag_to_int::<N>(&mag, neg, "attempt to multiply with overflow")
}
#[cfg(test)]
mod overflow_tests {
use super::mul_widen_divide;
use crate::int::types::Int;
use crate::support::rounding::RoundingMode;
fn at_scale(value: i128, k: u32) -> Int<3> {
let mut p = Int::<3>::from_i128(value);
let ten = Int::<3>::from_i128(10);
for _ in 0..k {
p = p.wrapping_mul(ten);
}
p
}
#[test]
fn mul_widen_divide_d57_overflow_panics_in_range_exact() {
let mode = RoundingMode::HalfToEven;
let got = mul_widen_divide::<3, 56>(at_scale(3, 56), at_scale(4, 56), mode);
assert_eq!(got, at_scale(12, 56), "in-range D57<56> product must be exact");
let a = at_scale(15, 56);
let b = at_scale(13, 56);
let r = std::panic::catch_unwind(|| mul_widen_divide::<3, 56>(a, b, mode));
assert!(r.is_err(), "D57<56> 15*13=195 out of range must panic, not wrap");
let neg_big = Int::<3>::ZERO.wrapping_sub(at_scale(2_219_290_601, 48)); let three = Int::<3>::from_i128(3);
let r0 = std::panic::catch_unwind(|| mul_widen_divide::<3, 0>(neg_big, three, mode));
assert!(r0.is_err(), "D57<0> (-2.2e57)*3 overflow must panic, not wrap");
}
}