simplex_trace/
rational.rs1use std::fmt;
4use std::iter::Sum;
5use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
6use std::str::FromStr;
7
8use num_bigint::BigInt;
9use num_rational::BigRational;
10use num_traits::{One, Signed, ToPrimitive, Zero};
11
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct Rational(BigRational);
39
40impl Rational {
41 pub fn new(numer: i64, denom: i64) -> Self {
43 assert!(denom != 0, "denominator is zero");
44 Rational(BigRational::new(BigInt::from(numer), BigInt::from(denom)))
45 }
46
47 pub fn zero() -> Self {
48 Rational(BigRational::zero())
49 }
50
51 pub fn one() -> Self {
52 Rational(BigRational::one())
53 }
54
55 pub fn is_zero(&self) -> bool {
56 self.0.is_zero()
57 }
58
59 pub fn is_positive(&self) -> bool {
60 self.0.is_positive()
61 }
62
63 pub fn is_negative(&self) -> bool {
64 self.0.is_negative()
65 }
66
67 pub fn is_integer(&self) -> bool {
69 self.0.is_integer()
70 }
71
72 pub fn abs(&self) -> Self {
73 Rational(self.0.abs())
74 }
75
76 pub fn to_i64_parts(&self) -> Option<(i64, i64)> {
79 Some((self.0.numer().to_i64()?, self.0.denom().to_i64()?))
80 }
81
82 pub fn to_f64(&self) -> f64 {
84 self.0.to_f64().unwrap_or(f64::NAN)
85 }
86}
87
88impl Default for Rational {
89 fn default() -> Self {
90 Rational::zero()
91 }
92}
93
94impl From<i32> for Rational {
95 fn from(n: i32) -> Self {
96 Rational(BigRational::from_integer(BigInt::from(n)))
97 }
98}
99
100impl From<i64> for Rational {
101 fn from(n: i64) -> Self {
102 Rational(BigRational::from_integer(BigInt::from(n)))
103 }
104}
105
106impl fmt::Display for Rational {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 fmt::Display::fmt(&self.0, f)
109 }
110}
111
112impl fmt::Debug for Rational {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 fmt::Display::fmt(&self.0, f)
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct ParseRationalError {
123 reason: String,
124}
125
126impl fmt::Display for ParseRationalError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 f.write_str(&self.reason)
129 }
130}
131
132impl std::error::Error for ParseRationalError {}
133
134impl FromStr for Rational {
137 type Err = ParseRationalError;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 s.parse::<BigRational>()
141 .map(Rational)
142 .map_err(|e| ParseRationalError {
143 reason: e.to_string(),
144 })
145 }
146}
147
148macro_rules! binary_op {
149 ($trait:ident, $method:ident, $assign_trait:ident, $assign_method:ident) => {
150 impl $trait<Rational> for Rational {
151 type Output = Rational;
152 fn $method(self, rhs: Rational) -> Rational {
153 Rational(self.0.$method(rhs.0))
154 }
155 }
156 impl $trait<&Rational> for Rational {
157 type Output = Rational;
158 fn $method(self, rhs: &Rational) -> Rational {
159 Rational(self.0.$method(&rhs.0))
160 }
161 }
162 impl $trait<Rational> for &Rational {
163 type Output = Rational;
164 fn $method(self, rhs: Rational) -> Rational {
165 Rational((&self.0).$method(rhs.0))
166 }
167 }
168 impl $trait<&Rational> for &Rational {
169 type Output = Rational;
170 fn $method(self, rhs: &Rational) -> Rational {
171 Rational((&self.0).$method(&rhs.0))
172 }
173 }
174 impl $assign_trait<Rational> for Rational {
175 fn $assign_method(&mut self, rhs: Rational) {
176 self.0.$assign_method(rhs.0);
177 }
178 }
179 impl $assign_trait<&Rational> for Rational {
180 fn $assign_method(&mut self, rhs: &Rational) {
181 self.0.$assign_method(&rhs.0);
182 }
183 }
184 };
185}
186
187binary_op!(Add, add, AddAssign, add_assign);
188binary_op!(Sub, sub, SubAssign, sub_assign);
189binary_op!(Mul, mul, MulAssign, mul_assign);
190binary_op!(Div, div, DivAssign, div_assign);
192
193impl Neg for Rational {
194 type Output = Rational;
195 fn neg(self) -> Rational {
196 Rational(-self.0)
197 }
198}
199
200impl Neg for &Rational {
201 type Output = Rational;
202 fn neg(self) -> Rational {
203 Rational(-&self.0)
204 }
205}
206
207impl Sum for Rational {
208 fn sum<I: Iterator<Item = Rational>>(iter: I) -> Rational {
209 iter.fold(Rational::zero(), |acc, x| acc + x)
210 }
211}
212
213impl<'a> Sum<&'a Rational> for Rational {
214 fn sum<I: Iterator<Item = &'a Rational>>(iter: I) -> Rational {
215 iter.fold(Rational::zero(), |acc, x| acc + x)
216 }
217}
218
219#[cfg(feature = "num-interop")]
220impl From<BigRational> for Rational {
221 fn from(r: BigRational) -> Self {
222 Rational(r)
223 }
224}
225
226#[cfg(feature = "num-interop")]
227impl From<Rational> for BigRational {
228 fn from(r: Rational) -> Self {
229 r.0
230 }
231}
232
233#[cfg(feature = "num-interop")]
234impl Rational {
235 pub fn as_big_rational(&self) -> &BigRational {
237 &self.0
238 }
239}
240
241#[cfg(feature = "serde")]
242impl serde::Serialize for Rational {
243 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
244 s.collect_str(self)
245 }
246}
247
248#[cfg(feature = "serde")]
249impl<'de> serde::Deserialize<'de> for Rational {
250 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
251 let s = String::deserialize(d)?;
252 s.trim()
253 .parse()
254 .map_err(|e| serde::de::Error::custom(format!("invalid rational {s:?}: {e}")))
255 }
256}