Skip to content

Commit 74c898b

Browse files
committed
Handle to_f64() with raw division by zero
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.
1 parent 2ad63fb commit 74c898b

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
@@ -1382,7 +1382,12 @@ macro_rules! to_primitive_small {
13821382
}
13831383

13841384
fn to_f64(&self) -> Option<f64> {
1385-
Some(self.numer.to_f64().unwrap() / self.denom.to_f64().unwrap())
1385+
let float = self.numer.to_f64().unwrap() / self.denom.to_f64().unwrap();
1386+
if float.is_nan() {
1387+
None
1388+
} else {
1389+
Some(float)
1390+
}
13861391
}
13871392
}
13881393
)*)
@@ -1415,10 +1420,15 @@ macro_rules! to_primitive_64 {
14151420
}
14161421

14171422
fn to_f64(&self) -> Option<f64> {
1418-
Some(ratio_to_f64(
1423+
let float = ratio_to_f64(
14191424
self.numer as i128,
14201425
self.denom as i128
1421-
))
1426+
);
1427+
if float.is_nan() {
1428+
None
1429+
} else {
1430+
Some(float)
1431+
}
14221432
}
14231433
}
14241434
)*)
@@ -1449,16 +1459,21 @@ impl<T: Clone + Integer + ToPrimitive + ToBigInt> ToPrimitive for Ratio<T> {
14491459
}
14501460

14511461
fn to_f64(&self) -> Option<f64> {
1452-
match (self.numer.to_i64(), self.denom.to_i64()) {
1453-
(Some(numer), Some(denom)) => Some(ratio_to_f64(
1462+
let float = match (self.numer.to_i64(), self.denom.to_i64()) {
1463+
(Some(numer), Some(denom)) => ratio_to_f64(
14541464
<i128 as From<_>>::from(numer),
14551465
<i128 as From<_>>::from(denom),
1456-
)),
1466+
),
14571467
_ => {
14581468
let numer: BigInt = self.numer.to_bigint()?;
14591469
let denom: BigInt = self.denom.to_bigint()?;
1460-
Some(ratio_to_f64(numer, denom))
1470+
ratio_to_f64(numer, denom)
14611471
}
1472+
};
1473+
if float.is_nan() {
1474+
None
1475+
} else {
1476+
Some(float)
14621477
}
14631478
}
14641479
}
@@ -1498,10 +1513,9 @@ fn ratio_to_f64<T: Bits + Clone + Integer + Signed + ShlAssign<usize> + ToPrimit
14981513
const MAX_EXACT_INT: i64 = 1i64 << core::f64::MANTISSA_DIGITS;
14991514
const MIN_EXACT_INT: i64 = -MAX_EXACT_INT;
15001515

1501-
let flo_sign = numer.signum().to_f64().unwrap() * denom.signum().to_f64().unwrap();
1502-
1503-
if numer.is_zero() {
1504-
return 0.0 * flo_sign;
1516+
let flo_sign = numer.signum().to_f64().unwrap() / denom.signum().to_f64().unwrap();
1517+
if !flo_sign.is_normal() {
1518+
return flo_sign;
15051519
}
15061520

15071521
// Fast track: both sides can losslessly be converted to f64s. In this case, letting the
@@ -2890,47 +2904,67 @@ mod test {
28902904
.unwrap(),
28912905
"3".parse().unwrap()
28922906
)
2893-
.to_f64()
2894-
.unwrap(),
2895-
411522630329218100000000000000000000000000000f64
2907+
.to_f64(),
2908+
Some(411522630329218100000000000000000000000000000f64)
2909+
);
2910+
assert_eq!(
2911+
BigRational::new(BigInt::one(), BigInt::one() << 1050).to_f64(),
2912+
Some(0f64)
28962913
);
28972914
assert_eq!(
2898-
BigRational::new(1.into(), BigInt::one() << 1050,)
2899-
.to_f64()
2900-
.unwrap(),
2901-
0f64
2915+
BigRational::from(BigInt::one() << 1050).to_f64(),
2916+
Some(core::f64::INFINITY)
2917+
);
2918+
assert_eq!(
2919+
BigRational::from((-BigInt::one()) << 1050).to_f64(),
2920+
Some(core::f64::NEG_INFINITY)
29022921
);
29032922
assert_eq!(
29042923
BigRational::new(
29052924
"1234567890987654321234567890".parse().unwrap(),
29062925
"987654321234567890987654321".parse().unwrap()
29072926
)
2908-
.to_f64()
2909-
.unwrap(),
2910-
1.2499999893125f64
2927+
.to_f64(),
2928+
Some(1.2499999893125f64)
2929+
);
2930+
assert_eq!(
2931+
BigRational::new_raw(BigInt::one(), BigInt::zero()).to_f64(),
2932+
Some(core::f64::INFINITY)
2933+
);
2934+
assert_eq!(
2935+
BigRational::new_raw(-BigInt::one(), BigInt::zero()).to_f64(),
2936+
Some(core::f64::NEG_INFINITY)
2937+
);
2938+
assert_eq!(
2939+
BigRational::new_raw(BigInt::zero(), BigInt::zero()).to_f64(),
2940+
None
29112941
);
29122942
}
29132943

29142944
#[test]
29152945
fn test_ratio_to_f64() {
2916-
assert_eq!(0.5f64, Ratio::<u8>::new(1, 2).to_f64().unwrap());
2917-
assert_eq!(0.5f64, Rational64::new(1, 2).to_f64().unwrap());
2918-
assert_eq!(-0.5f64, Rational64::new(1, -2).to_f64().unwrap());
2919-
assert_eq!(0.0f64, Rational64::new(0, 2).to_f64().unwrap());
2920-
assert_eq!(-0.0f64, Rational64::new(0, -2).to_f64().unwrap());
2946+
assert_eq!(Ratio::<u8>::new(1, 2).to_f64(), Some(0.5f64));
2947+
assert_eq!(Rational64::new(1, 2).to_f64(), Some(0.5f64));
2948+
assert_eq!(Rational64::new(1, -2).to_f64(), Some(-0.5f64));
2949+
assert_eq!(Rational64::new(0, 2).to_f64(), Some(0.0f64));
2950+
assert_eq!(Rational64::new(0, -2).to_f64(), Some(-0.0f64));
2951+
assert_eq!(Rational64::new((1 << 57) + 1, 1 << 54).to_f64(), Some(8f64));
29212952
assert_eq!(
2922-
8f64,
2923-
Rational64::new((1 << 57) + 1, 1 << 54).to_f64().unwrap()
2953+
Rational64::new((1 << 52) + 1, 1 << 52).to_f64(),
2954+
Some(1.0000000000000002f64),
29242955
);
29252956
assert_eq!(
2926-
1.0000000000000002f64,
2927-
Rational64::new((1 << 52) + 1, 1 << 52).to_f64().unwrap()
2957+
Rational64::new((1 << 60) + (1 << 8), 1 << 60).to_f64(),
2958+
Some(1.0000000000000002f64),
29282959
);
29292960
assert_eq!(
2930-
1.0000000000000002f64,
2931-
Rational64::new((1 << 60) + (1 << 8), 1 << 60)
2932-
.to_f64()
2933-
.unwrap()
2961+
Ratio::<i32>::new_raw(1, 0).to_f64(),
2962+
Some(core::f64::INFINITY)
2963+
);
2964+
assert_eq!(
2965+
Ratio::<i32>::new_raw(-1, 0).to_f64(),
2966+
Some(core::f64::NEG_INFINITY)
29342967
);
2968+
assert_eq!(Ratio::<i32>::new_raw(0, 0).to_f64(), None);
29352969
}
29362970
}

0 commit comments

Comments
 (0)