From e468255657a620ce74ee68e19b6774dc0a527a71 Mon Sep 17 00:00:00 2001 From: Jay Lorch Date: Mon, 27 Jul 2026 10:22:28 -0700 Subject: [PATCH] fix: Handle i64::MIN / -1 special case Rust panics on i64::MIN % -1i64, so Number::divide needs to handle it specially. --- src/number.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/number.rs b/src/number.rs index 22fc044..a860446 100644 --- a/src/number.rs +++ b/src/number.rs @@ -591,7 +591,11 @@ impl Number { } } (Number::Int(a), Number::Int(b)) => { - if *a % *b == 0 { + if *a == i64::MIN && *b == -1 { + // Rust panics on i64::MIN % -1i64, so handle it specially + let quotient = BigInt::from(*a) / BigInt::from(*b); + Ok(Number::from_bigint_owned(quotient)) + } else if *a % *b == 0 { if let Some(q) = a.checked_div(*b) { Ok(Number::Int(q)) } else { @@ -991,3 +995,41 @@ fn scientific_parts_to_bigint(mantissa: &str, exponent: i32) -> Option { Some(value) } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] // tests expect() to assert arithmetic results + + use super::*; + + /// Regression test: `i64::MIN / -1` overflows `i64` and panics in Rust's + /// native integer division/remainder. `divide` must promote the result + /// instead of panicking. + #[test] + fn i64_min_by_negative_one() { + let quotient = Number::Int(i64::MIN) + .divide(&Number::Int(-1)) + .expect("division should succeed"); + + // 2^63 does not fit in i64, but does fit in u64. + assert_eq!(quotient.as_u64(), Some(9_223_372_036_854_775_808)); + assert_eq!(quotient.as_i64(), None); + assert_eq!(quotient.as_i128(), Some(9_223_372_036_854_775_808)); + assert_eq!( + *quotient.to_big().expect("to_big should succeed"), + -BigInt::from(i64::MIN) + ); + + // The same overflow case reached via the mixed `Int`/`BigInt` path. + let big_quotient = Number::Int(i64::MIN) + .divide(&Number::BigInt(Rc::new(BigInt::from(-1)))) + .expect("division should succeed"); + assert_eq!(big_quotient.as_u64(), Some(9_223_372_036_854_775_808)); + + // `i64::MIN % -1` also panics natively; the result must be zero. + let remainder = Number::Int(i64::MIN) + .modulo(&Number::Int(-1)) + .expect("modulo should succeed"); + assert_eq!(remainder.as_i64(), Some(0)); + } +}