commonware_utils/
rational.rs

1//! Utilities for working with `num_rational::BigRational`.
2
3use num_bigint::BigInt;
4use num_integer::Integer;
5use num_rational::BigRational;
6use num_traits::{ToPrimitive, Zero};
7
8/// Extension trait adding convenience constructors for [`BigRational`].
9pub trait BigRationalExt {
10    /// Creates a [`BigRational`] from a `u64` numerator with denominator `1`.
11    fn from_u64(value: u64) -> Self;
12
13    /// Creates a [`BigRational`] from a `u64` numerator and denominator.
14    fn from_frac_u64(numerator: u64, denominator: u64) -> Self;
15
16    /// Creates a [`BigRational`] from a `u128` numerator with denominator `1`.
17    fn from_u128(value: u128) -> Self;
18
19    /// Creates a [`BigRational`] from a `u128` numerator and denominator.
20    fn from_frac_u128(numerator: u128, denominator: u128) -> Self;
21
22    /// Returns the ceiling of the rational value as `u128`, saturating and treating negative values as zero.
23    fn ceil_to_u128(&self) -> Option<u128>;
24
25    /// Creates a [`BigRational`] from a `usize` numerator with denominator `1`.
26    fn from_usize(value: usize) -> Self;
27
28    /// Creates a [`BigRational`] from a `usize` numerator and denominator.
29    fn from_frac_usize(numerator: usize, denominator: usize) -> Self;
30}
31
32impl BigRationalExt for BigRational {
33    fn from_u64(value: u64) -> Self {
34        BigRational::from_integer(BigInt::from(value))
35    }
36
37    fn from_frac_u64(numerator: u64, denominator: u64) -> Self {
38        BigRational::new(BigInt::from(numerator), BigInt::from(denominator))
39    }
40
41    fn from_u128(value: u128) -> Self {
42        BigRational::from_integer(BigInt::from(value))
43    }
44
45    fn from_frac_u128(numerator: u128, denominator: u128) -> Self {
46        BigRational::new(BigInt::from(numerator), BigInt::from(denominator))
47    }
48
49    fn ceil_to_u128(&self) -> Option<u128> {
50        if self < &BigRational::zero() {
51            return Some(0);
52        }
53
54        let den = self.denom();
55        if den.is_zero() {
56            return None;
57        }
58
59        let (quot, rem) = self.numer().div_rem(den);
60        let mut result = quot.to_u128().unwrap_or(u128::MAX);
61        if !rem.is_zero() {
62            result = result.saturating_add(1);
63        }
64        Some(result)
65    }
66
67    fn from_usize(value: usize) -> Self {
68        BigRational::from_integer(BigInt::from(value))
69    }
70
71    fn from_frac_usize(numerator: usize, denominator: usize) -> Self {
72        BigRational::new(BigInt::from(numerator), BigInt::from(denominator))
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::BigRationalExt;
79    use num_bigint::BigInt;
80    use num_rational::BigRational;
81
82    #[test]
83    fn converts_from_u64() {
84        let rational = BigRational::from_u64(42);
85        assert_eq!(rational.numer(), &BigInt::from(42u64));
86        assert_eq!(rational.denom(), &BigInt::from(1u32));
87    }
88
89    #[test]
90    fn converts_from_frac_u64() {
91        let rational = BigRational::from_frac_u64(6, 8);
92        assert_eq!(rational.numer(), &BigInt::from(3u32));
93        assert_eq!(rational.denom(), &BigInt::from(4u32));
94    }
95
96    #[test]
97    fn converts_from_u128() {
98        let value = (u64::MAX as u128) + 10;
99        let rational = BigRational::from_u128(value);
100        assert_eq!(rational.numer(), &BigInt::from(value));
101        assert_eq!(rational.denom(), &BigInt::from(1u32));
102    }
103
104    #[test]
105    fn converts_from_frac_u128() {
106        let rational = BigRational::from_frac_u128(10, 4);
107        assert_eq!(rational.numer(), &BigInt::from(5u32));
108        assert_eq!(rational.denom(), &BigInt::from(2u32));
109    }
110
111    #[test]
112    fn converts_from_usize() {
113        let value = usize::MAX;
114        let rational = BigRational::from_usize(value);
115        assert_eq!(rational.numer(), &BigInt::from(value));
116        assert_eq!(rational.denom(), &BigInt::from(1u32));
117    }
118
119    #[test]
120    fn converts_from_frac_usize() {
121        let rational = BigRational::from_frac_usize(48, 18);
122        assert_eq!(rational.numer(), &BigInt::from(8u32));
123        assert_eq!(rational.denom(), &BigInt::from(3u32));
124    }
125
126    #[test]
127    fn ceiling_handles_positive_fraction() {
128        let value = BigRational::new(BigInt::from(5u32), BigInt::from(2u32));
129        assert_eq!(value.ceil_to_u128(), Some(3));
130    }
131
132    #[test]
133    fn ceiling_handles_negative() {
134        let value = BigRational::new(BigInt::from(-3i32), BigInt::from(2u32));
135        assert_eq!(value.ceil_to_u128(), Some(0));
136    }
137
138    #[test]
139    fn ceiling_handles_large_values() {
140        let value = BigRational::from_u128(u128::MAX - 1);
141        assert_eq!(value.ceil_to_u128(), Some(u128::MAX - 1));
142    }
143}