Skip to content

Commit 2a567d3

Browse files
bors[bot]cuviper
andauthored
Merge #83
83: Handle to_f64() with raw division by zero r=cuviper a=cuviper You're not really supposed to have a zero denominator, but it's possible with `Ratio::new_raw`, and we can treat that more carefully when converting to floats. If the numerator is also zero, return `None` rather than using NaN. Otherwise, return `Some(±infinity)` matching the sign of the numerator. Co-authored-by: Josh Stone <cuviper@gmail.com>
2 parents 78a35f1 + 74c898b commit 2a567d3

File tree

1 file changed

+68
-34
lines changed

1 file changed

+68
-34
lines changed

src/lib.rs

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,7 +1391,12 @@ macro_rules! to_primitive_small {
13911391
}
13921392

13931393
fn to_f64(&self) -> Option<f64> {
1394-
Some(self.numer.to_f64().unwrap() / self.denom.to_f64().unwrap())
1394+
let float = self.numer.to_f64().unwrap() / self.denom.to_f64().unwrap();
1395+
if float.is_nan() {
1396+
None
1397+
} else {
1398+
Some(float)
1399+
}
13951400
}
13961401
}
13971402
)*)
@@ -1424,10 +1429,15 @@ macro_rules! to_primitive_64 {
14241429
}
14251430

14261431
fn to_f64(&self) -> Option<f64> {
1427-
Some(ratio_to_f64(
1432+
let float = ratio_to_f64(
14281433
self.numer as i128,
14291434
self.denom as i128
1430-
))
1435+
);
1436+
if float.is_nan() {
1437+
None
1438+
} else {
1439+
Some(float)
1440+
}
14311441
}
14321442
}
14331443
)*)
@@ -1458,16 +1468,21 @@ impl<T: Clone + Integer + ToPrimitive + ToBigInt> ToPrimitive for Ratio<T> {
14581468
}
14591469

14601470
fn to_f64(&self) -> Option<f64> {
1461-
match (self.numer.to_i64(), self.denom.to_i64()) {
1462-
(Some(numer), Some(denom)) => Some(ratio_to_f64(
1471+
let float = match (self.numer.to_i64(), self.denom.to_i64()) {
1472+
(Some(numer), Some(denom)) => ratio_to_f64(
14631473
<i128 as From<_>>::from(numer),
14641474
<i128 as From<_>>::from(denom),
1465-
)),
1475+
),
14661476
_ => {
14671477
let numer: BigInt = self.numer.to_bigint()?;
14681478
let denom: BigInt = self.denom.to_bigint()?;
1469-
Some(ratio_to_f64(numer, denom))
1479+
ratio_to_f64(numer, denom)
14701480
}
1481+
};
1482+
if float.is_nan() {
1483+
None
1484+
} else {
1485+
Some(float)
14711486
}
14721487
}
14731488
}
@@ -1507,10 +1522,9 @@ fn ratio_to_f64<T: Bits + Clone + Integer + Signed + ShlAssign<usize> + ToPrimit
15071522
const MAX_EXACT_INT: i64 = 1i64 << core::f64::MANTISSA_DIGITS;
15081523
const MIN_EXACT_INT: i64 = -MAX_EXACT_INT;
15091524

1510-
let flo_sign = numer.signum().to_f64().unwrap() * denom.signum().to_f64().unwrap();
1511-
1512-
if numer.is_zero() {
1513-
return 0.0 * flo_sign;
1525+
let flo_sign = numer.signum().to_f64().unwrap() / denom.signum().to_f64().unwrap();
1526+
if !flo_sign.is_normal() {
1527+
return flo_sign;
15141528
}
15151529

15161530
// Fast track: both sides can losslessly be converted to f64s. In this case, letting the
@@ -2899,47 +2913,67 @@ mod test {
28992913
.unwrap(),
29002914
"3".parse().unwrap()
29012915
)
2902-
.to_f64()
2903-
.unwrap(),
2904-
411522630329218100000000000000000000000000000f64
2916+
.to_f64(),
2917+
Some(411522630329218100000000000000000000000000000f64)
2918+
);
2919+
assert_eq!(
2920+
BigRational::new(BigInt::one(), BigInt::one() << 1050).to_f64(),
2921+
Some(0f64)
29052922
);
29062923
assert_eq!(
2907-
BigRational::new(1.into(), BigInt::one() << 1050,)
2908-
.to_f64()
2909-
.unwrap(),
2910-
0f64
2924+
BigRational::from(BigInt::one() << 1050).to_f64(),
2925+
Some(core::f64::INFINITY)
2926+
);
2927+
assert_eq!(
2928+
BigRational::from((-BigInt::one()) << 1050).to_f64(),
2929+
Some(core::f64::NEG_INFINITY)
29112930
);
29122931
assert_eq!(
29132932
BigRational::new(
29142933
"1234567890987654321234567890".parse().unwrap(),
29152934
"987654321234567890987654321".parse().unwrap()
29162935
)
2917-
.to_f64()
2918-
.unwrap(),
2919-
1.2499999893125f64
2936+
.to_f64(),
2937+
Some(1.2499999893125f64)
2938+
);
2939+
assert_eq!(
2940+
BigRational::new_raw(BigInt::one(), BigInt::zero()).to_f64(),
2941+
Some(core::f64::INFINITY)
2942+
);
2943+
assert_eq!(
2944+
BigRational::new_raw(-BigInt::one(), BigInt::zero()).to_f64(),
2945+
Some(core::f64::NEG_INFINITY)
2946+
);
2947+
assert_eq!(
2948+
BigRational::new_raw(BigInt::zero(), BigInt::zero()).to_f64(),
2949+
None
29202950
);
29212951
}
29222952

29232953
#[test]
29242954
fn test_ratio_to_f64() {
2925-
assert_eq!(0.5f64, Ratio::<u8>::new(1, 2).to_f64().unwrap());
2926-
assert_eq!(0.5f64, Rational64::new(1, 2).to_f64().unwrap());
2927-
assert_eq!(-0.5f64, Rational64::new(1, -2).to_f64().unwrap());
2928-
assert_eq!(0.0f64, Rational64::new(0, 2).to_f64().unwrap());
2929-
assert_eq!(-0.0f64, Rational64::new(0, -2).to_f64().unwrap());
2955+
assert_eq!(Ratio::<u8>::new(1, 2).to_f64(), Some(0.5f64));
2956+
assert_eq!(Rational64::new(1, 2).to_f64(), Some(0.5f64));
2957+
assert_eq!(Rational64::new(1, -2).to_f64(), Some(-0.5f64));
2958+
assert_eq!(Rational64::new(0, 2).to_f64(), Some(0.0f64));
2959+
assert_eq!(Rational64::new(0, -2).to_f64(), Some(-0.0f64));
2960+
assert_eq!(Rational64::new((1 << 57) + 1, 1 << 54).to_f64(), Some(8f64));
29302961
assert_eq!(
2931-
8f64,
2932-
Rational64::new((1 << 57) + 1, 1 << 54).to_f64().unwrap()
2962+
Rational64::new((1 << 52) + 1, 1 << 52).to_f64(),
2963+
Some(1.0000000000000002f64),
29332964
);
29342965
assert_eq!(
2935-
1.0000000000000002f64,
2936-
Rational64::new((1 << 52) + 1, 1 << 52).to_f64().unwrap()
2966+
Rational64::new((1 << 60) + (1 << 8), 1 << 60).to_f64(),
2967+
Some(1.0000000000000002f64),
29372968
);
29382969
assert_eq!(
2939-
1.0000000000000002f64,
2940-
Rational64::new((1 << 60) + (1 << 8), 1 << 60)
2941-
.to_f64()
2942-
.unwrap()
2970+
Ratio::<i32>::new_raw(1, 0).to_f64(),
2971+
Some(core::f64::INFINITY)
2972+
);
2973+
assert_eq!(
2974+
Ratio::<i32>::new_raw(-1, 0).to_f64(),
2975+
Some(core::f64::NEG_INFINITY)
29432976
);
2977+
assert_eq!(Ratio::<i32>::new_raw(0, 0).to_f64(), None);
29442978
}
29452979
}

0 commit comments

Comments
 (0)