Skip to content

Commit 2c2ed15

Browse files
bors[bot]cuviper
andauthored
Merge #77
77: Fix CheckedDiv with minimum values r=cuviper a=cuviper Co-authored-by: Josh Stone <cuviper@gmail.com>
2 parents aab0c70 + 6903b6b commit 2c2ed15

File tree

1 file changed

+127
-16
lines changed

1 file changed

+127
-16
lines changed

src/lib.rs

Lines changed: 127 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ impl<T: Clone + Integer> Ratio<T> {
9090
/// Creates a new `Ratio`. Fails if `denom` is zero.
9191
#[inline]
9292
pub fn new(numer: T, denom: T) -> Ratio<T> {
93-
if denom.is_zero() {
94-
panic!("denominator == 0");
95-
}
9693
let mut ret = Ratio::new_raw(numer, denom);
9794
ret.reduce();
9895
ret
@@ -118,6 +115,17 @@ impl<T: Clone + Integer> Ratio<T> {
118115

119116
/// Puts self into lowest terms, with denom > 0.
120117
fn reduce(&mut self) {
118+
if self.denom.is_zero() {
119+
panic!("denominator == 0");
120+
}
121+
if self.numer.is_zero() {
122+
self.denom.set_one();
123+
return;
124+
}
125+
if self.numer == self.denom {
126+
self.set_one();
127+
return;
128+
}
121129
let g: T = self.numer.gcd(&self.denom);
122130

123131
// FIXME(#5992): assignment operator overloads
@@ -776,17 +784,40 @@ where
776784
if rhs.is_zero() {
777785
return None;
778786
}
779-
let gcd_ac = self.numer.gcd(&rhs.numer);
780-
let gcd_bd = self.denom.gcd(&rhs.denom);
781-
let denom = (self.denom.clone() / gcd_bd.clone())
782-
.checked_mul(&(rhs.numer.clone() / gcd_ac.clone()))?;
787+
let (numer, denom) = if self.denom == rhs.denom {
788+
(self.numer.clone(), rhs.numer.clone())
789+
} else if self.numer == rhs.numer {
790+
(rhs.denom.clone(), self.denom.clone())
791+
} else {
792+
let gcd_ac = self.numer.gcd(&rhs.numer);
793+
let gcd_bd = self.denom.gcd(&rhs.denom);
794+
(
795+
(self.numer.clone() / gcd_ac.clone())
796+
.checked_mul(&(rhs.denom.clone() / gcd_bd.clone()))?,
797+
(self.denom.clone() / gcd_bd).checked_mul(&(rhs.numer.clone() / gcd_ac))?,
798+
)
799+
};
800+
// Manual `reduce()`, avoiding sharp edges
783801
if denom.is_zero() {
784-
return None;
802+
None
803+
} else if numer.is_zero() {
804+
Some(Self::zero())
805+
} else if numer == denom {
806+
Some(Self::one())
807+
} else {
808+
let g = numer.gcd(&denom);
809+
let numer = numer / g.clone();
810+
let denom = denom / g;
811+
let raw = if denom < T::zero() {
812+
// We need to keep denom positive, but 2's-complement MIN may
813+
// overflow negation -- instead we can check multiplying -1.
814+
let n1 = T::zero() - T::one();
815+
Ratio::new_raw(numer.checked_mul(&n1)?, denom.checked_mul(&n1)?)
816+
} else {
817+
Ratio::new_raw(numer, denom)
818+
};
819+
Some(raw)
785820
}
786-
Some(Ratio::new(
787-
(self.numer.clone() / gcd_ac).checked_mul(&(rhs.denom.clone() / gcd_bd))?,
788-
denom,
789-
))
790821
}
791822
}
792823

@@ -1295,6 +1326,7 @@ mod test {
12951326

12961327
use core::f64;
12971328
use core::i32;
1329+
use core::isize;
12981330
use core::str::FromStr;
12991331
use num_integer::Integer;
13001332
use num_traits::{FromPrimitive, One, Pow, Signed, Zero};
@@ -1331,6 +1363,22 @@ mod test {
13311363
numer: -2,
13321364
denom: 3,
13331365
};
1366+
pub const _MIN: Rational = Ratio {
1367+
numer: isize::MIN,
1368+
denom: 1,
1369+
};
1370+
pub const _MIN_P1: Rational = Ratio {
1371+
numer: isize::MIN + 1,
1372+
denom: 1,
1373+
};
1374+
pub const _MAX: Rational = Ratio {
1375+
numer: isize::MAX,
1376+
denom: 1,
1377+
};
1378+
pub const _MAX_M1: Rational = Ratio {
1379+
numer: isize::MAX - 1,
1380+
denom: 1,
1381+
};
13341382

13351383
#[cfg(feature = "bigint")]
13361384
pub fn to_big(n: Rational) -> BigRational {
@@ -1361,9 +1409,9 @@ mod test {
13611409

13621410
#[test]
13631411
fn test_new_reduce() {
1364-
let one22 = Ratio::new(2, 2);
1365-
1366-
assert_eq!(one22, One::one());
1412+
assert_eq!(Ratio::new(2, 2), One::one());
1413+
assert_eq!(Ratio::new(0, i32::MIN), Zero::zero());
1414+
assert_eq!(Ratio::new(i32::MIN, i32::MIN), One::one());
13671415
}
13681416
#[test]
13691417
#[should_panic]
@@ -1521,7 +1569,7 @@ mod test {
15211569

15221570
mod arith {
15231571
use super::super::{Ratio, Rational};
1524-
use super::{to_big, _0, _1, _1_2, _2, _3_2, _5_2, _NEG1_2};
1572+
use super::{to_big, _0, _1, _1_2, _2, _3_2, _5_2, _MAX, _MAX_M1, _MIN, _MIN_P1, _NEG1_2};
15251573
use core::fmt::Debug;
15261574
use num_integer::Integer;
15271575
use num_traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, NumAssign};
@@ -1953,6 +2001,69 @@ mod test {
19532001
assert_eq!(_0.checked_mul(&_0), Some(_0));
19542002
assert_eq!(_0.checked_div(&_0), None);
19552003
}
2004+
2005+
#[test]
2006+
fn test_checked_min() {
2007+
assert_eq!(_MIN.checked_add(&_MIN), None);
2008+
assert_eq!(_MIN.checked_sub(&_MIN), Some(_0));
2009+
assert_eq!(_MIN.checked_mul(&_MIN), None);
2010+
assert_eq!(_MIN.checked_div(&_MIN), Some(_1));
2011+
assert_eq!(_0.checked_add(&_MIN), Some(_MIN));
2012+
assert_eq!(_0.checked_sub(&_MIN), None);
2013+
assert_eq!(_0.checked_mul(&_MIN), Some(_0));
2014+
assert_eq!(_0.checked_div(&_MIN), Some(_0));
2015+
assert_eq!(_1.checked_add(&_MIN), Some(_MIN_P1));
2016+
assert_eq!(_1.checked_sub(&_MIN), None);
2017+
assert_eq!(_1.checked_mul(&_MIN), Some(_MIN));
2018+
assert_eq!(_1.checked_div(&_MIN), None);
2019+
assert_eq!(_MIN.checked_add(&_0), Some(_MIN));
2020+
assert_eq!(_MIN.checked_sub(&_0), Some(_MIN));
2021+
assert_eq!(_MIN.checked_mul(&_0), Some(_0));
2022+
assert_eq!(_MIN.checked_div(&_0), None);
2023+
assert_eq!(_MIN.checked_add(&_1), Some(_MIN_P1));
2024+
assert_eq!(_MIN.checked_sub(&_1), None);
2025+
assert_eq!(_MIN.checked_mul(&_1), Some(_MIN));
2026+
assert_eq!(_MIN.checked_div(&_1), Some(_MIN));
2027+
}
2028+
2029+
#[test]
2030+
fn test_checked_max() {
2031+
assert_eq!(_MAX.checked_add(&_MAX), None);
2032+
assert_eq!(_MAX.checked_sub(&_MAX), Some(_0));
2033+
assert_eq!(_MAX.checked_mul(&_MAX), None);
2034+
assert_eq!(_MAX.checked_div(&_MAX), Some(_1));
2035+
assert_eq!(_0.checked_add(&_MAX), Some(_MAX));
2036+
assert_eq!(_0.checked_sub(&_MAX), Some(_MIN_P1));
2037+
assert_eq!(_0.checked_mul(&_MAX), Some(_0));
2038+
assert_eq!(_0.checked_div(&_MAX), Some(_0));
2039+
assert_eq!(_1.checked_add(&_MAX), None);
2040+
assert_eq!(_1.checked_sub(&_MAX), Some(-_MAX_M1));
2041+
assert_eq!(_1.checked_mul(&_MAX), Some(_MAX));
2042+
assert_eq!(_1.checked_div(&_MAX), Some(_MAX.recip()));
2043+
assert_eq!(_MAX.checked_add(&_0), Some(_MAX));
2044+
assert_eq!(_MAX.checked_sub(&_0), Some(_MAX));
2045+
assert_eq!(_MAX.checked_mul(&_0), Some(_0));
2046+
assert_eq!(_MAX.checked_div(&_0), None);
2047+
assert_eq!(_MAX.checked_add(&_1), None);
2048+
assert_eq!(_MAX.checked_sub(&_1), Some(_MAX_M1));
2049+
assert_eq!(_MAX.checked_mul(&_1), Some(_MAX));
2050+
assert_eq!(_MAX.checked_div(&_1), Some(_MAX));
2051+
}
2052+
2053+
#[test]
2054+
fn test_checked_min_max() {
2055+
assert_eq!(_MIN.checked_add(&_MAX), Some(-_1));
2056+
assert_eq!(_MIN.checked_sub(&_MAX), None);
2057+
assert_eq!(_MIN.checked_mul(&_MAX), None);
2058+
assert_eq!(
2059+
_MIN.checked_div(&_MAX),
2060+
Some(Ratio::new(_MIN.numer, _MAX.numer))
2061+
);
2062+
assert_eq!(_MAX.checked_add(&_MIN), Some(-_1));
2063+
assert_eq!(_MAX.checked_sub(&_MIN), None);
2064+
assert_eq!(_MAX.checked_mul(&_MIN), None);
2065+
assert_eq!(_MAX.checked_div(&_MIN), None);
2066+
}
19562067
}
19572068

19582069
#[test]

0 commit comments

Comments
 (0)