1use std::fmt::{Display, Debug};
10use std::str::FromStr;
11use std::cmp;
12use std::ops::{Mul, Add, Sub, Neg, AddAssign, SubAssign, MulAssign, Div, DivAssign, Rem, RemAssign};
13use num_traits::{Zero, One};
14use auto_impl_ops::auto_ops;
15
16use crate::abst::{EucRing, EucRingOps, MathType, Mon, AddMon, AddGrp, AddMonOps, AddGrpOps, MonOps, RingOps, Ring, FieldOps, Field};
17use crate::util::parse_err::ParseErr;
18use super::int::{IntType, IntOps};
19
20#[derive(Copy, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr))]
23pub struct Ratio<T> {
24 numer: T,
25 denom: T,
26}
27
28impl<T> Ratio<T> {
29 #[inline]
30 const fn new_raw(numer: T, denom: T) -> Ratio<T> {
31 Ratio { numer, denom }
32 }
33
34 #[inline]
35 pub const fn numer(&self) -> &T {
36 &self.numer
37 }
38
39 #[inline]
40 pub const fn denom(&self) -> &T {
41 &self.denom
42 }
43}
44
45impl<T> Ratio<T>
46where T: EucRing, for<'x> &'x T: EucRingOps<T> {
47 #[inline]
48 pub fn new(numer: T, denom: T) -> Ratio<T> {
49 assert!(!denom.is_zero());
50
51 let mut ret = Ratio::new_raw(numer, denom);
52 ret.reduce();
53 ret
54 }
55
56 fn reduce(&mut self) {
57 if self.numer.is_zero() {
58 if !self.denom.is_one() {
59 self.denom.set_one();
60 }
61 return;
62 }
63
64 let u = self.denom.normalizing_unit();
65
66 if !u.is_one() {
67 self.numer *= &u;
68 self.denom *= &u;
69 }
70
71 if self.denom.is_one() || self.numer.is_unit() {
72 return
73 }
74
75 let g = EucRing::gcd(&self.numer, &self.denom); if !g.is_one() {
78 self.numer /= &g;
79 self.denom /= &g;
80 }
81 }
82
83 pub fn is_int(&self) -> bool {
84 self.denom.is_one()
85 }
86}
87
88impl<T> From<T> for Ratio<T>
89where T: One {
90 fn from(a: T) -> Self {
91 Self::new_raw(a, T::one())
92 }
93}
94
95impl<T> From<(T, T)> for Ratio<T>
96where T: EucRing, for<'x> &'x T: EucRingOps<T> {
97 fn from(pair: (T, T)) -> Self {
98 let (p, q) = pair;
99 Self::new(p, q)
100 }
101}
102
103impl<T> FromStr for Ratio<T>
104where T: EucRing + FromStr, for<'x> &'x T: EucRingOps<T> {
105 type Err = ParseErr;
106
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 if let Ok(a) = s.parse::<T>() {
109 return Ok(Self::from(a))
110 }
111
112 let r = regex::Regex::new(r"(.+)/(.+)").unwrap();
113 if let Some(c) = r.captures(s) {
114 let (s1, s2) = (&c[1], &c[2]);
115 if let (Ok(a), Ok(b)) = (s1.parse::<T>(), s2.parse::<T>()) {
116 if b.is_zero() {
117 return Err(ParseErr::new(format!("zero denominator in \"{s}\"")))
118 }
119 return Ok(Self::new(a, b))
120 }
121 }
122
123 Err(ParseErr::invalid(s, &Self::math_symbol()))
124 }
125}
126
127impl<T> Default for Ratio<T>
128where T: Default + One {
129 fn default() -> Self {
130 Self::from(T::default())
131 }
132}
133
134impl<T> Display for Ratio<T>
135where T: Display {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 use crate::util::format::paren_expr;
138
139 let p = paren_expr(&self.numer);
140 let q = paren_expr(&self.denom);
141
142 if &q == "1" {
143 write!(f, "{}", p)
144 } else {
145 write!(f, "{}/{}", p, q)
146 }
147 }
148}
149
150impl<T> Debug for Ratio<T>
151where T: Display {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 Display::fmt(&self, f)
154 }
155}
156
157impl<T> Zero for Ratio<T>
158where T: EucRing, for<'x> &'x T: EucRingOps<T> {
159 fn zero() -> Self {
160 Self::from(T::zero())
161 }
162
163 fn is_zero(&self) -> bool {
164 self.numer.is_zero()
165 }
166}
167
168impl<T> One for Ratio<T>
169where T: EucRing, for<'x> &'x T: EucRingOps<T> {
170 fn one() -> Self {
171 Self::from(T::one())
172 }
173
174 fn is_one(&self) -> bool {
175 self.numer == self.denom
176 }
177}
178
179macro_rules! impl_add_assign_op {
180 ($trait:ident, $method:ident) => {
181 #[auto_ops]
182 impl<T> $trait<&Ratio<T>> for Ratio<T>
183 where T: EucRing, for<'x> &'x T: EucRingOps<T> {
184 fn $method(&mut self, rhs: &Ratio<T>) {
185 let (_, b) = (&self.numer, &self.denom);
186 let (c, d) = ( &rhs.numer, &rhs.denom);
187
188 if rhs.is_zero() {
189 } else if self.is_zero() {
191 self.numer.$method(c); self.denom = d.clone(); } else if b == d {
194 self.numer.$method(c); self.reduce()
196 } else {
197 let l = EucRing::lcm(b, d); self.numer *= (&l / b); self.numer.$method((&l / d) * c);
200 self.denom = l; self.reduce()
202 }
203 }
204 }
205 };
206}
207
208impl_add_assign_op!(AddAssign, add_assign);
209impl_add_assign_op!(SubAssign, sub_assign);
210
211impl<T> Neg for Ratio<T>
212where T: EucRing, for<'x> &'x T: EucRingOps<T> {
213 type Output = Self;
214 fn neg(self) -> Self::Output {
215 Ratio::new(-&self.numer, self.denom)
216 }
217}
218
219impl<T> Neg for &Ratio<T>
220where T: EucRing, for<'x> &'x T: EucRingOps<T> {
221 type Output = Ratio<T>;
222 fn neg(self) -> Self::Output {
223 Ratio::new(-&self.numer, self.denom.clone())
224 }
225}
226
227#[auto_ops]
228impl<T> MulAssign<&Ratio<T>> for Ratio<T>
229where T: EucRing, for<'x> &'x T: EucRingOps<T> {
230 fn mul_assign(&mut self, rhs: &Ratio<T>) {
231 let (a, b) = (&self.numer, &self.denom);
232 let (c, d) = ( &rhs.numer, &rhs.denom);
233
234 if self.is_zero() || rhs.is_one() {
235 } else if rhs.is_zero() {
237 self.set_zero(); } else if rhs.is_int() {
239 let k = EucRing::gcd(b, c); self.numer *= c / &k; self.denom /= &k; } else if self.is_int() {
243 let k = EucRing::gcd(a, d); self.numer /= &k; self.numer *= c; self.denom = d / &k; } else {
248 let k = EucRing::gcd(a, d); let l = EucRing::gcd(b, c); self.numer /= &k; self.numer *= c / &l; self.denom /= &l; self.denom *= d / &k; }
255 }
256}
257
258#[auto_ops]
259impl<T> DivAssign<&Ratio<T>> for Ratio<T>
260where T: EucRing, for<'x> &'x T: EucRingOps<T> {
261 fn div_assign(&mut self, rhs: &Ratio<T>) {
262 assert!(!rhs.is_zero());
263 *self *= rhs.inv().unwrap()
264 }
265}
266
267#[auto_ops]
268impl<T> Rem<&Ratio<T>> for &Ratio<T>
269where T: EucRing, for<'x> &'x T: EucRingOps<T> {
270 type Output = Ratio<T>;
271 fn rem(self, rhs: &Ratio<T>) -> Self::Output {
272 assert!(!rhs.is_zero());
273 Ratio::zero() }
275}
276
277macro_rules! decl_alg_ops {
278 ($trait:ident) => {
279 impl<T> $trait for Ratio<T>
280 where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
281
282 impl<T> $trait<Ratio<T>> for &Ratio<T>
283 where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
284 };
285}
286
287decl_alg_ops!(AddMonOps);
288decl_alg_ops!(AddGrpOps);
289decl_alg_ops!(MonOps);
290decl_alg_ops!(RingOps);
291decl_alg_ops!(EucRingOps);
292decl_alg_ops!(FieldOps);
293
294impl<T> MathType for Ratio<T>
295where T: EucRing, for<'x> &'x T: EucRingOps<T> {
296 fn math_symbol() -> String {
297 let t = T::math_symbol();
298 if &t == "Z" {
299 String::from("Q")
300 } else {
301 format!("Q({})", T::math_symbol())
302 }
303 }
304}
305
306impl<T> Mon for Ratio<T>
307where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
308
309impl<T> AddMon for Ratio<T>
310where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
311
312impl<T> AddGrp for Ratio<T>
313where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
314
315impl<T> Ring for Ratio<T>
316where T: EucRing, for<'x> &'x T: EucRingOps<T> {
317 fn inv(&self) -> Option<Self> {
318 if self.is_zero() {
319 None
320 } else {
321 let inv = Self::new(self.denom.clone(), self.numer.clone());
322 Some(inv)
323 }
324 }
325
326 fn is_unit(&self) -> bool {
327 !self.is_zero()
328 }
329
330 fn normalizing_unit(&self) -> Self {
331 if self.is_zero() {
332 Self::one()
333 } else {
334 self.inv().unwrap()
335 }
336 }
337
338 fn c_weight(&self) -> f64 {
339 f64::max(self.numer.c_weight(), self.denom.c_weight())
340 }
341}
342
343impl<T> EucRing for Ratio<T>
344where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
345
346impl<T> Field for Ratio<T>
347where T: EucRing, for<'x> &'x T: EucRingOps<T> {}
348
349impl<T> Ratio<T>
350where T: IntType, for<'x> &'x T: IntOps<T> {
351 pub fn abs(&self) -> Self {
352 if self.numer.is_negative() {
353 -self
354 } else {
355 self.clone()
356 }
357 }
358
359 pub fn to_f64(&self) -> f64 {
360 let p = self.numer.to_f64().unwrap();
361 let q = self.denom.to_f64().unwrap();
362 p / q
363 }
364}
365
366impl<T> Ord for Ratio<T>
367where T: IntType, for<'x> &'x T: IntOps<T> {
368 fn cmp(&self, other: &Self) -> cmp::Ordering {
369 (self.numer() * other.denom()).cmp(&(other.numer() * self.denom()))
371 }
372}
373
374impl<T> PartialOrd for Ratio<T>
375where T: IntType, for<'x> &'x T: IntOps<T> {
376 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
377 Some(self.cmp(other))
378 }
379}
380
381mod tex {
382 use crate::util::tex::TeX;
383 use super::*;
384
385 impl<T> TeX for Ratio<T>
386 where T: TeX + MathType {
387 fn tex_math_symbol() -> String {
388 let t = T::math_symbol();
389 if &t == "Z" {
390 String::from("\\mathbb{Q}")
391 } else {
392 format!("Q({})", T::math_symbol())
393 }
394 }
395
396 fn tex_string(&self) -> String {
397 let p = self.numer.tex_string();
398 let q = self.denom.tex_string();
399
400 if &q == "1" {
401 p
402 } else if !p.starts_with('-') && !p.contains(' ') {
403 format!(r"\frac{{{p}}}{{{q}}}")
404 } else {
405 let p = p.strip_prefix('-').unwrap();
406 format!(r"-\frac{{{p}}}{{{q}}}")
407 }
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn math_symbol() {
418 assert_eq!(Ratio::<i32>::math_symbol(), "Q");
419 }
420
421 #[test]
422 fn constants() {
423 assert_eq!(Ratio::zero(), Ratio::new_raw(0, 1));
424 assert_eq!(Ratio::one(), Ratio::new_raw(1, 1));
425 }
426
427 #[test]
428 fn reduce() {
429 let a = Ratio::new(0, -4);
430 assert_eq!(a.numer, 0);
431 assert_eq!(a.denom, 1);
432
433 let a = Ratio::new(-3, 1);
434 assert_eq!(a.numer, -3);
435 assert_eq!(a.denom, 1);
436
437 let a = Ratio::new(1, -3);
438 assert_eq!(a.numer, -1);
439 assert_eq!(a.denom, 3);
440
441 let a = Ratio::new(6, -8);
442 assert_eq!(a.numer, -3);
443 assert_eq!(a.denom, 4);
444 }
445
446 #[test]
447 fn display() {
448 assert_eq!(format!("{}", Ratio::new(-3, 1)), "-3");
449 assert_eq!(format!("{}", Ratio::new(-3, 4)), "-3/4");
450 }
451
452 #[test]
453 fn debug() {
454 assert_eq!(format!("{:?}", Ratio::new(-3, 1)), "-3");
455 assert_eq!(format!("{:?}", Ratio::new(-3, 4)), "-3/4");
456 }
457
458 #[test]
459 fn add() {
460 let a = Ratio::new(1, 2);
461 let b = Ratio::new(3, 5);
462 assert_eq!(a + b, Ratio::new(11, 10));
463
464 let a = Ratio::new(1, 2);
465 let o = Ratio::zero();
466 assert_eq!(a + o, a);
467 assert_eq!(o + a, a);
468
469 let a = Ratio::new(1, 3);
470 let b = Ratio::new(2, 3);
471 assert_eq!(a + b, Ratio::new(1, 1));
472
473 let a = Ratio::new(1, 6);
474 let b = Ratio::new(1, 3);
475 assert_eq!(a + b, Ratio::new(1, 2));
476 }
477
478 #[test]
479 fn add_assign() {
480 let mut a = Ratio::new(1, 2);
481 a += Ratio::new(3, 5);
482
483 assert_eq!(a, Ratio::new(11, 10));
484 }
485
486 #[test]
487 fn neg() {
488 let a = Ratio::new(1, 2);
489 assert_eq!(-a, Ratio::new(-1, 2));
490 }
491
492 #[test]
493 fn sub() {
494 let a = Ratio::new(1, 2);
495 let b = Ratio::new(3, 5);
496 assert_eq!(a - b, Ratio::new(-1, 10));
497
498 let a = Ratio::new(1, 2);
499 let o = Ratio::zero();
500 assert_eq!(a - o, a);
501 assert_eq!(o - a, -a);
502 }
503
504 #[test]
505 fn sub_assign() {
506 let mut a = Ratio::new(1, 2);
507 a -= Ratio::new(3, 5);
508 assert_eq!(a, Ratio::new(-1, 10));
509 }
510
511 #[test]
512 fn mul() {
513 let a = Ratio::new(3, 10);
514 let b = Ratio::new(-2, 7);
515 assert_eq!(a * b, Ratio::new(-3, 35));
516
517 let a = Ratio::new(3, 4);
518 let e = Ratio::one();
519 assert_eq!(a * e, a);
520 assert_eq!(e * a, a);
521
522 let a = Ratio::new(3, 4);
523 let e = -Ratio::one();
524 assert_eq!(a * e, -a);
525 assert_eq!(e * a, -a);
526
527 let a = Ratio::new(3, 4);
528 let o = Ratio::zero();
529 assert_eq!(a * o, Ratio::zero());
530 assert_eq!(o * a, Ratio::zero());
531 }
532
533 #[test]
534 fn mul_assign() {
535 let mut a = Ratio::new(3, 10);
536 a *= Ratio::new(2, 7);
537 assert_eq!(a, Ratio::new(3, 35));
538 }
539
540 #[test]
541 fn div() {
542 let a = Ratio::new(3, 10);
543 let b = Ratio::new(2, 7);
544 assert_eq!(a / b, Ratio::new(21, 20));
545 }
546
547 #[test]
548 fn div_assign() {
549 let mut a = Ratio::new(3, 10);
550 a /= Ratio::new(2, 7);
551 assert_eq!(a, Ratio::new(21, 20));
552 }
553
554 #[test]
555 fn rem() {
556 let a = Ratio::new(3, 10);
557 let b = Ratio::new(2, 7);
558 assert_eq!(a % b, Ratio::zero());
559 }
560
561 #[test]
562 fn rem_assign() {
563 let mut a = Ratio::new(3, 10);
564 a %= Ratio::new(2, 7);
565 assert_eq!(a, Ratio::zero());
566 }
567
568 #[test]
569 fn inv() {
570 let a = Ratio::new(-3, 10);
571 assert_eq!(a.inv(), Some(Ratio::new(-10, 3)));
572
573 let a = Ratio::<i32>::zero();
574 assert_eq!(a.inv(), None);
575 }
576
577 #[test]
578 fn is_unit() {
579 let a = Ratio::new(-3, 10);
580 assert!(a.is_unit());
581
582 let a = Ratio::<i32>::zero();
583 assert!(!a.is_unit());
584 }
585
586 #[test]
587 fn normalizing_unit() {
588 let a = Ratio::new(-3, 10);
589 assert_eq!(a.normalizing_unit(), Ratio::new(-10, 3));
590
591 let a = Ratio::<i32>::zero();
592 assert_eq!(a.normalizing_unit(), Ratio::one());
593 }
594
595 #[test]
596 fn gcd_normalized() {
597 let (x, y) = (Ratio::new(-3, 2), Ratio::new(5, 4));
599
600 let d = EucRing::gcd(&x, &y);
601 assert_eq!(d, Ratio::one(), "gcd is not normalized");
602
603 let (d, s, t) = EucRing::gcdx(&x, &y);
604 assert_eq!(d, Ratio::one(), "gcdx's d is not normalized");
605 assert_eq!(s * x + t * y, d, "Bezout fails");
606 assert_eq!(EucRing::gcd(&x, &y), d, "gcd disagrees with gcdx");
607 }
608
609 #[test]
610 fn cmp() {
611 let a = Ratio::new(3, 5);
612 let b = Ratio::new(4, 7);
613 assert!(a > b);
614 }
615
616 #[test]
617 fn from_str_zero_denom() {
618 assert!(Ratio::<i64>::from_str("1/0").is_err());
620 assert!(Ratio::<i64>::from_str("0/0").is_err());
621 assert_eq!(Ratio::<i64>::from_str("1/2"), Ok(Ratio::new(1, 2)));
622 }
623
624 #[test]
625 fn cmp_large() {
626 let a = Ratio::new((1i64 << 53) + 1, 1);
628 let b = Ratio::new(1i64 << 53, 1);
629 assert_ne!(a, b);
630 assert!(a > b);
631
632 let c = Ratio::new(1i64, (1i64 << 53) + 1);
633 let d = Ratio::new(1i64, 1i64 << 53);
634 assert_ne!(c, d);
635 assert!(c < d);
636 }
637
638 #[test]
639 fn c_weight() {
640 let a = Ratio::new(-43, 31);
641 assert_eq!(a.c_weight(), 43_f64);
642
643 let a = Ratio::new(34, 123);
644 assert_eq!(a.c_weight(), 123_f64);
645 }
646
647 #[test]
648 #[cfg(feature = "serde")]
649 fn serialize() {
650 let a = Ratio::new(3, 5);
651
652 let ser = serde_json::to_string(&a).unwrap();
653 assert_eq!(ser, "\"3/5\"");
654
655 let deser = serde_json::from_str::<Ratio<i32>>(&ser).unwrap();
656 assert_eq!(a, deser);
657 }
658
659 #[test]
660 fn tex() {
661 use crate::util::tex::TeX;
662 assert_eq!(Ratio::<i32>::tex_math_symbol(), "\\mathbb{Q}");
663
664 let a = Ratio::new(43, 1);
665 let b = Ratio::new(43, 31);
666 let c = Ratio::new(-43, 31);
667
668 assert_eq!(a.tex_string(), "43");
669 assert_eq!(b.tex_string(), r"\frac{43}{31}");
670 assert_eq!(c.tex_string(), r"-\frac{43}{31}");
671 }
672}