qualia_core_db/solvers/exact/
rational.rs1use core::cmp::Ordering;
12use core::fmt;
13
14use super::bigint::BigInt;
15
16#[derive(Clone, PartialEq, Eq)]
18pub struct BigRational {
19 num: BigInt,
20 den: BigInt, }
22
23impl BigRational {
24 pub fn zero() -> Self {
26 BigRational {
27 num: BigInt::zero(),
28 den: BigInt::one(),
29 }
30 }
31
32 pub fn one() -> Self {
34 BigRational {
35 num: BigInt::one(),
36 den: BigInt::one(),
37 }
38 }
39
40 pub fn from_bigint(n: BigInt) -> Self {
42 BigRational {
43 num: n,
44 den: BigInt::one(),
45 }
46 }
47
48 pub fn from_i64(n: i64) -> Self {
50 BigRational::from_bigint(BigInt::from_i64(n))
51 }
52
53 pub fn from_i64s(num: i64, den: i64) -> Option<Self> {
56 Self::new(BigInt::from_i64(num), BigInt::from_i64(den))
57 }
58
59 pub fn new(num: BigInt, den: BigInt) -> Option<Self> {
62 if den.is_zero() {
63 return None; }
65 let mut num = num;
66 let mut den = den;
67 if den.is_negative() {
69 num = num.neg();
70 den = den.neg();
71 }
72 if num.is_zero() {
73 return Some(BigRational {
74 num: BigInt::zero(),
75 den: BigInt::one(),
76 });
77 }
78 let g = num.gcd(&den); let num = num.div(&g).expect("g non-zero");
80 let den = den.div(&g).expect("g non-zero");
81 Some(BigRational { num, den })
82 }
83
84 pub fn numerator(&self) -> &BigInt {
86 &self.num
87 }
88
89 pub fn denominator(&self) -> &BigInt {
91 &self.den
92 }
93
94 pub fn is_zero(&self) -> bool {
96 self.num.is_zero()
97 }
98
99 pub fn signum(&self) -> i8 {
101 self.num.signum()
102 }
103
104 pub fn abs(&self) -> Self {
106 BigRational {
107 num: self.num.abs(),
108 den: self.den.clone(),
109 }
110 }
111
112 pub fn neg(&self) -> Self {
114 BigRational {
115 num: self.num.neg(),
116 den: self.den.clone(),
117 }
118 }
119
120 pub fn recip(&self) -> Option<Self> {
122 if self.is_zero() {
123 return None;
124 }
125 BigRational::new(self.den.clone(), self.num.clone())
126 }
127
128 pub fn add(&self, other: &BigRational) -> BigRational {
130 let num = self.num.mul(&other.den).add(&other.num.mul(&self.den));
131 let den = self.den.mul(&other.den);
132 BigRational::new(num, den).expect("product of positive denominators is non-zero")
133 }
134
135 pub fn sub(&self, other: &BigRational) -> BigRational {
137 self.add(&other.neg())
138 }
139
140 pub fn mul(&self, other: &BigRational) -> BigRational {
142 let num = self.num.mul(&other.num);
143 let den = self.den.mul(&other.den);
144 BigRational::new(num, den).expect("product of positive denominators is non-zero")
145 }
146
147 pub fn div(&self, other: &BigRational) -> Option<BigRational> {
149 if other.is_zero() {
150 return None;
151 }
152 let num = self.num.mul(&other.den);
153 let den = self.den.mul(&other.num);
154 BigRational::new(num, den)
155 }
156
157 pub fn to_f64(&self) -> f64 {
159 let n = parse_bigint_f64(&self.num);
162 let d = parse_bigint_f64(&self.den);
163 n / d
164 }
165}
166
167fn parse_bigint_f64(b: &BigInt) -> f64 {
170 b.to_string().parse::<f64>().unwrap_or_else(|_| {
171 if b.is_negative() {
173 f64::NEG_INFINITY
174 } else {
175 f64::INFINITY
176 }
177 })
178}
179
180impl PartialOrd for BigRational {
181 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
182 Some(self.cmp(other))
183 }
184}
185
186impl Ord for BigRational {
187 fn cmp(&self, other: &Self) -> Ordering {
188 let lhs = self.num.mul(&other.den);
190 let rhs = other.num.mul(&self.den);
191 lhs.cmp(&rhs)
192 }
193}
194
195impl fmt::Debug for BigRational {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 write!(f, "BigRational({}/{})", self.num, self.den)
198 }
199}
200
201impl fmt::Display for BigRational {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 if self.den == BigInt::one() {
204 write!(f, "{}", self.num)
205 } else {
206 write!(f, "{}/{}", self.num, self.den)
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn reduces_on_construction() {
217 let r = BigRational::from_i64s(2, 4).unwrap();
219 assert_eq!(r.numerator().to_string(), "1");
220 assert_eq!(r.denominator().to_string(), "2");
221 }
222
223 #[test]
224 fn sign_normalisation() {
225 let r = BigRational::from_i64s(1, -2).unwrap();
227 assert_eq!(r.numerator().to_string(), "-1");
228 assert_eq!(r.denominator().to_string(), "2");
229 let r2 = BigRational::from_i64s(-3, -6).unwrap();
231 assert_eq!(r2.numerator().to_string(), "1");
232 assert_eq!(r2.denominator().to_string(), "2");
233 }
234
235 #[test]
236 fn third_plus_sixth_is_half() {
237 let a = BigRational::from_i64s(1, 3).unwrap();
238 let b = BigRational::from_i64s(1, 6).unwrap();
239 let s = a.add(&b);
240 assert_eq!(s, BigRational::from_i64s(1, 2).unwrap());
241 }
242
243 #[test]
244 fn third_plus_two_thirds_is_one() {
245 let a = BigRational::from_i64s(1, 3).unwrap();
246 let b = BigRational::from_i64s(2, 3).unwrap();
247 let s = a.add(&b);
248 assert_eq!(s, BigRational::one());
249 assert_eq!(s.numerator().to_string(), "1");
250 assert_eq!(s.denominator().to_string(), "1");
251 }
252
253 #[test]
254 fn sub_mul_div() {
255 let a = BigRational::from_i64s(3, 4).unwrap();
256 let b = BigRational::from_i64s(1, 4).unwrap();
257 assert_eq!(a.sub(&b), BigRational::from_i64s(1, 2).unwrap());
258 assert_eq!(a.mul(&b), BigRational::from_i64s(3, 16).unwrap());
259 assert_eq!(a.div(&b).unwrap(), BigRational::from_i64(3));
260 }
261
262 #[test]
263 fn div_by_zero_fails_closed() {
264 let a = BigRational::from_i64(5);
265 assert!(a.div(&BigRational::zero()).is_none());
266 assert!(BigRational::new(BigInt::from_i64(1), BigInt::zero()).is_none());
267 assert!(BigRational::from_i64s(1, 0).is_none());
268 assert!(BigRational::zero().recip().is_none());
269 }
270
271 #[test]
272 fn ordering() {
273 let third = BigRational::from_i64s(1, 3).unwrap();
274 let half = BigRational::from_i64s(1, 2).unwrap();
275 assert!(third < half);
276 assert!(half.neg() < third);
277 assert_eq!(
278 third.cmp(&BigRational::from_i64s(2, 6).unwrap()),
279 Ordering::Equal
280 );
281 }
282
283 #[test]
284 fn to_f64_values() {
285 assert!((BigRational::from_i64s(1, 2).unwrap().to_f64() - 0.5).abs() < 1e-15);
286 assert!((BigRational::from_i64s(1, 4).unwrap().to_f64() - 0.25).abs() < 1e-15);
287 assert!((BigRational::from_i64s(-3, 4).unwrap().to_f64() + 0.75).abs() < 1e-15);
288 }
289
290 #[test]
291 fn exact_large_arithmetic() {
292 let third = BigRational::from_i64s(1, 3).unwrap();
294 let s = third.add(&third).add(&third);
295 assert_eq!(s, BigRational::one());
296 }
297}