Skip to content

Commit 07112ea

Browse files
bors[bot]MattX
andauthored
Merge #52
52: Convert Ratio of integers to f64 r=cuviper a=MattX Performs the division with integer arithmetic, and rounds before performing an exact cast to float. This avoids any issues with rounding. General method referenced from https://stackoverflow.com/questions/33623875/converting-an-arbitrary-precision-rational-number-ocaml-zarith-to-an-approxim. Partially fixes #4 (no support for `f32` conversion currently). Co-authored-by: Matthieu Felix <matthieufelix@gmail.com>
2 parents 47d63e2 + 7dc11ea commit 07112ea

File tree

1 file changed

+295
-2
lines changed

1 file changed

+295
-2
lines changed

src/lib.rs

Lines changed: 295 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,17 @@ use core::cmp;
2828
use core::fmt;
2929
use core::fmt::{Binary, Display, Formatter, LowerExp, LowerHex, Octal, UpperExp, UpperHex};
3030
use core::hash::{Hash, Hasher};
31-
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
31+
use core::ops::{Add, Div, Mul, Neg, Rem, ShlAssign, Sub};
3232
use core::str::FromStr;
3333
#[cfg(feature = "std")]
3434
use std::error::Error;
3535

3636
#[cfg(feature = "bigint")]
37-
use num_bigint::{BigInt, BigUint, Sign};
37+
use num_bigint::{BigInt, BigUint, Sign, ToBigInt};
3838

3939
use num_integer::Integer;
4040
use num_traits::float::FloatCore;
41+
use num_traits::ToPrimitive;
4142
use num_traits::{
4243
Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, Inv, Num, NumCast, One,
4344
Pow, Signed, Zero,
@@ -1360,6 +1361,223 @@ where
13601361
Some(Ratio::new(n1, d1))
13611362
}
13621363

1364+
#[cfg(not(feature = "bigint"))]
1365+
macro_rules! to_primitive_small {
1366+
($($type_name:ty)*) => ($(
1367+
impl ToPrimitive for Ratio<$type_name> {
1368+
fn to_i64(&self) -> Option<i64> {
1369+
self.to_integer().to_i64()
1370+
}
1371+
1372+
fn to_i128(&self) -> Option<i128> {
1373+
self.to_integer().to_i128()
1374+
}
1375+
1376+
fn to_u64(&self) -> Option<u64> {
1377+
self.to_integer().to_u64()
1378+
}
1379+
1380+
fn to_u128(&self) -> Option<u128> {
1381+
self.to_integer().to_u128()
1382+
}
1383+
1384+
fn to_f64(&self) -> Option<f64> {
1385+
Some(self.numer.to_f64().unwrap() / self.denom.to_f64().unwrap())
1386+
}
1387+
}
1388+
)*)
1389+
}
1390+
1391+
#[cfg(not(feature = "bigint"))]
1392+
to_primitive_small!(u8 i8 u16 i16 u32 i32);
1393+
1394+
#[cfg(all(target_pointer_width = "32", not(feature = "bigint")))]
1395+
to_primitive_small!(usize isize);
1396+
1397+
#[cfg(not(feature = "bigint"))]
1398+
macro_rules! to_primitive_64 {
1399+
($($type_name:ty)*) => ($(
1400+
impl ToPrimitive for Ratio<$type_name> {
1401+
fn to_i64(&self) -> Option<i64> {
1402+
self.to_integer().to_i64()
1403+
}
1404+
1405+
fn to_i128(&self) -> Option<i128> {
1406+
self.to_integer().to_i128()
1407+
}
1408+
1409+
fn to_u64(&self) -> Option<u64> {
1410+
self.to_integer().to_u64()
1411+
}
1412+
1413+
fn to_u128(&self) -> Option<u128> {
1414+
self.to_integer().to_u128()
1415+
}
1416+
1417+
fn to_f64(&self) -> Option<f64> {
1418+
Some(ratio_to_f64(
1419+
self.numer as i128,
1420+
self.denom as i128
1421+
))
1422+
}
1423+
}
1424+
)*)
1425+
}
1426+
1427+
#[cfg(not(feature = "bigint"))]
1428+
to_primitive_64!(u64 i64);
1429+
1430+
#[cfg(all(target_pointer_width = "64", not(feature = "bigint")))]
1431+
to_primitive_64!(usize isize);
1432+
1433+
#[cfg(feature = "bigint")]
1434+
impl<T: Clone + Integer + ToPrimitive + ToBigInt> ToPrimitive for Ratio<T> {
1435+
fn to_i64(&self) -> Option<i64> {
1436+
self.to_integer().to_i64()
1437+
}
1438+
1439+
fn to_i128(&self) -> Option<i128> {
1440+
self.to_integer().to_i128()
1441+
}
1442+
1443+
fn to_u64(&self) -> Option<u64> {
1444+
self.to_integer().to_u64()
1445+
}
1446+
1447+
fn to_u128(&self) -> Option<u128> {
1448+
self.to_integer().to_u128()
1449+
}
1450+
1451+
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(
1454+
<i128 as From<_>>::from(numer),
1455+
<i128 as From<_>>::from(denom),
1456+
)),
1457+
_ => {
1458+
let numer: BigInt = self.numer.to_bigint()?;
1459+
let denom: BigInt = self.denom.to_bigint()?;
1460+
Some(ratio_to_f64(numer, denom))
1461+
}
1462+
}
1463+
}
1464+
}
1465+
1466+
trait Bits {
1467+
fn bits(&self) -> usize;
1468+
}
1469+
1470+
#[cfg(feature = "bigint")]
1471+
impl Bits for BigInt {
1472+
fn bits(&self) -> usize {
1473+
self.bits()
1474+
}
1475+
}
1476+
1477+
impl Bits for i128 {
1478+
fn bits(&self) -> usize {
1479+
(128 - self.wrapping_abs().leading_zeros()) as usize
1480+
}
1481+
}
1482+
1483+
/// Converts a ratio of `T` to an f64.
1484+
///
1485+
/// In addition to stated trait bounds, `T` must be able to hold numbers 56 bits larger than
1486+
/// the largest of `numer` and `denom`. This is automatically true if `T` is `BigInt`.
1487+
fn ratio_to_f64<T: Bits + Clone + Integer + Signed + ShlAssign<usize> + ToPrimitive>(
1488+
numer: T,
1489+
denom: T,
1490+
) -> f64 {
1491+
assert_eq!(
1492+
core::f64::RADIX,
1493+
2,
1494+
"only floating point implementations with radix 2 are supported"
1495+
);
1496+
1497+
// Inclusive upper and lower bounds to the range of exactly-representable ints in an f64.
1498+
const MAX_EXACT_INT: i64 = 1i64 << core::f64::MANTISSA_DIGITS;
1499+
const MIN_EXACT_INT: i64 = -MAX_EXACT_INT;
1500+
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;
1505+
}
1506+
1507+
// Fast track: both sides can losslessly be converted to f64s. In this case, letting the
1508+
// FPU do the job is faster and easier. In any other case, converting to f64s may lead
1509+
// to an inexact result: https://stackoverflow.com/questions/56641441/.
1510+
if let (Some(n), Some(d)) = (numer.to_i64(), denom.to_i64()) {
1511+
if MIN_EXACT_INT <= n && n <= MAX_EXACT_INT && MIN_EXACT_INT <= d && d <= MAX_EXACT_INT {
1512+
return n.to_f64().unwrap() / d.to_f64().unwrap();
1513+
}
1514+
}
1515+
1516+
// Otherwise, the goal is to obtain a quotient with at least 55 bits. 53 of these bits will
1517+
// be used as the mantissa of the resulting float, and the remaining two are for rounding.
1518+
// There's an error of up to 1 on the number of resulting bits, so we may get either 55 or
1519+
// 56 bits.
1520+
let mut numer = numer.abs();
1521+
let mut denom = denom.abs();
1522+
let (is_diff_positive, absolute_diff) = match numer.bits().checked_sub(denom.bits()) {
1523+
Some(diff) => (true, diff),
1524+
None => (false, denom.bits() - numer.bits()),
1525+
};
1526+
1527+
// Filter out overflows and underflows. After this step, the signed difference fits in an
1528+
// isize.
1529+
if is_diff_positive && absolute_diff > core::f64::MAX_EXP as usize {
1530+
return core::f64::INFINITY * flo_sign;
1531+
}
1532+
if !is_diff_positive
1533+
&& absolute_diff > -core::f64::MIN_EXP as usize + core::f64::MANTISSA_DIGITS as usize + 1
1534+
{
1535+
return 0.0 * flo_sign;
1536+
}
1537+
let diff = if is_diff_positive {
1538+
absolute_diff.to_isize().unwrap()
1539+
} else {
1540+
-absolute_diff.to_isize().unwrap()
1541+
};
1542+
1543+
// Shift is chosen so that the quotient will have 55 or 56 bits. The exception is if the
1544+
// quotient is going to be subnormal, in which case it may have fewer bits.
1545+
let shift: isize =
1546+
diff.max(core::f64::MIN_EXP as isize) - core::f64::MANTISSA_DIGITS as isize - 2;
1547+
if shift >= 0 {
1548+
denom <<= shift as usize
1549+
} else {
1550+
numer <<= -shift as usize
1551+
};
1552+
1553+
let (quotient, remainder) = numer.div_rem(&denom);
1554+
1555+
// This is guaranteed to fit since we've set up quotient to be at most 56 bits.
1556+
let mut quotient = quotient.to_u64().unwrap();
1557+
let n_rounding_bits = {
1558+
let quotient_bits = 64 - quotient.leading_zeros() as isize;
1559+
let subnormal_bits = core::f64::MIN_EXP as isize - shift;
1560+
quotient_bits.max(subnormal_bits) - core::f64::MANTISSA_DIGITS as isize
1561+
} as usize;
1562+
debug_assert!(n_rounding_bits == 2 || n_rounding_bits == 3);
1563+
let rounding_bit_mask = (1u64 << n_rounding_bits) - 1;
1564+
1565+
// Round to 53 bits with round-to-even. For rounding, we need to take into account both
1566+
// our rounding bits and the division's remainder.
1567+
let ls_bit = quotient & (1u64 << n_rounding_bits) != 0;
1568+
let ms_rounding_bit = quotient & (1u64 << (n_rounding_bits - 1)) != 0;
1569+
let ls_rounding_bits = quotient & (rounding_bit_mask >> 1) != 0;
1570+
if ms_rounding_bit && (ls_bit || ls_rounding_bits || !remainder.is_zero()) {
1571+
quotient += 1u64 << n_rounding_bits;
1572+
}
1573+
quotient &= !rounding_bit_mask;
1574+
1575+
// The quotient is guaranteed to be exactly representable as it's now 53 bits + 2 or 3
1576+
// trailing zeros, so there is no risk of a rounding error here.
1577+
let q_float = quotient as f64;
1578+
q_float * 2f64.powi(shift as i32) * flo_sign
1579+
}
1580+
13631581
#[cfg(test)]
13641582
#[cfg(feature = "std")]
13651583
fn hash<T: Hash>(x: &T) -> u64 {
@@ -1372,6 +1590,8 @@ fn hash<T: Hash>(x: &T) -> u64 {
13721590

13731591
#[cfg(test)]
13741592
mod test {
1593+
#[cfg(all(feature = "bigint"))]
1594+
use super::BigInt;
13751595
#[cfg(feature = "bigint")]
13761596
use super::BigRational;
13771597
use super::{Ratio, Rational, Rational64};
@@ -1381,6 +1601,7 @@ mod test {
13811601
use core::isize;
13821602
use core::str::FromStr;
13831603
use num_integer::Integer;
1604+
use num_traits::ToPrimitive;
13841605
use num_traits::{FromPrimitive, One, Pow, Signed, Zero};
13851606

13861607
pub const _0: Rational = Ratio { numer: 0, denom: 1 };
@@ -2640,4 +2861,76 @@ mod test {
26402861
assert_eq!(r.numer(), &(123 / 3));
26412862
assert_eq!(r.denom(), &(456 / 3));
26422863
}
2864+
2865+
#[test]
2866+
fn test_ratio_to_i64() {
2867+
assert_eq!(5, Rational64::new(70, 14).to_u64().unwrap());
2868+
assert_eq!(-3, Rational64::new(-31, 8).to_i64().unwrap());
2869+
assert_eq!(None, Rational64::new(-31, 8).to_u64());
2870+
}
2871+
2872+
#[test]
2873+
#[cfg(feature = "bigint")]
2874+
fn test_ratio_to_i128() {
2875+
assert_eq!(
2876+
1i128 << 70,
2877+
Ratio::<i128>::new(1i128 << 77, 1i128 << 7)
2878+
.to_i128()
2879+
.unwrap()
2880+
);
2881+
}
2882+
2883+
#[test]
2884+
#[cfg(feature = "bigint")]
2885+
fn test_big_ratio_to_f64() {
2886+
assert_eq!(
2887+
BigRational::new(
2888+
"1234567890987654321234567890987654321234567890"
2889+
.parse()
2890+
.unwrap(),
2891+
"3".parse().unwrap()
2892+
)
2893+
.to_f64()
2894+
.unwrap(),
2895+
411522630329218100000000000000000000000000000f64
2896+
);
2897+
assert_eq!(
2898+
BigRational::new(1.into(), BigInt::one() << 1050,)
2899+
.to_f64()
2900+
.unwrap(),
2901+
0f64
2902+
);
2903+
assert_eq!(
2904+
BigRational::new(
2905+
"1234567890987654321234567890".parse().unwrap(),
2906+
"987654321234567890987654321".parse().unwrap()
2907+
)
2908+
.to_f64()
2909+
.unwrap(),
2910+
1.2499999893125f64
2911+
);
2912+
}
2913+
2914+
#[test]
2915+
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());
2921+
assert_eq!(
2922+
8f64,
2923+
Rational64::new((1 << 57) + 1, 1 << 54).to_f64().unwrap()
2924+
);
2925+
assert_eq!(
2926+
1.0000000000000002f64,
2927+
Rational64::new((1 << 52) + 1, 1 << 52).to_f64().unwrap()
2928+
);
2929+
assert_eq!(
2930+
1.0000000000000002f64,
2931+
Rational64::new((1 << 60) + (1 << 8), 1 << 60)
2932+
.to_f64()
2933+
.unwrap()
2934+
);
2935+
}
26432936
}

0 commit comments

Comments
 (0)