Skip to content

Commit e468255

Browse files
authored
fix: Handle i64::MIN / -1 special case
Rust panics on i64::MIN % -1i64, so Number::divide needs to handle it specially.
1 parent 6608a9f commit e468255

1 file changed

Lines changed: 43 additions & 1 deletion

File tree

src/number.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,11 @@ impl Number {
591591
}
592592
}
593593
(Number::Int(a), Number::Int(b)) => {
594-
if *a % *b == 0 {
594+
if *a == i64::MIN && *b == -1 {
595+
// Rust panics on i64::MIN % -1i64, so handle it specially
596+
let quotient = BigInt::from(*a) / BigInt::from(*b);
597+
Ok(Number::from_bigint_owned(quotient))
598+
} else if *a % *b == 0 {
595599
if let Some(q) = a.checked_div(*b) {
596600
Ok(Number::Int(q))
597601
} else {
@@ -991,3 +995,41 @@ fn scientific_parts_to_bigint(mantissa: &str, exponent: i32) -> Option<BigInt> {
991995

992996
Some(value)
993997
}
998+
999+
#[cfg(test)]
1000+
mod tests {
1001+
#![allow(clippy::expect_used)] // tests expect() to assert arithmetic results
1002+
1003+
use super::*;
1004+
1005+
/// Regression test: `i64::MIN / -1` overflows `i64` and panics in Rust's
1006+
/// native integer division/remainder. `divide` must promote the result
1007+
/// instead of panicking.
1008+
#[test]
1009+
fn i64_min_by_negative_one() {
1010+
let quotient = Number::Int(i64::MIN)
1011+
.divide(&Number::Int(-1))
1012+
.expect("division should succeed");
1013+
1014+
// 2^63 does not fit in i64, but does fit in u64.
1015+
assert_eq!(quotient.as_u64(), Some(9_223_372_036_854_775_808));
1016+
assert_eq!(quotient.as_i64(), None);
1017+
assert_eq!(quotient.as_i128(), Some(9_223_372_036_854_775_808));
1018+
assert_eq!(
1019+
*quotient.to_big().expect("to_big should succeed"),
1020+
-BigInt::from(i64::MIN)
1021+
);
1022+
1023+
// The same overflow case reached via the mixed `Int`/`BigInt` path.
1024+
let big_quotient = Number::Int(i64::MIN)
1025+
.divide(&Number::BigInt(Rc::new(BigInt::from(-1))))
1026+
.expect("division should succeed");
1027+
assert_eq!(big_quotient.as_u64(), Some(9_223_372_036_854_775_808));
1028+
1029+
// `i64::MIN % -1` also panics natively; the result must be zero.
1030+
let remainder = Number::Int(i64::MIN)
1031+
.modulo(&Number::Int(-1))
1032+
.expect("modulo should succeed");
1033+
assert_eq!(remainder.as_i64(), Some(0));
1034+
}
1035+
}

0 commit comments

Comments
 (0)