1use std::fmt;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6pub struct Rational {
7 pub num: i64,
8 pub den: i64,
9}
10
11impl Rational {
12 pub const fn new(num: i64, den: i64) -> Self {
13 Self { num, den }
14 }
15
16 pub const fn zero() -> Self {
17 Self { num: 0, den: 1 }
18 }
19
20 pub fn is_zero(&self) -> bool {
21 self.num == 0
22 }
23
24 pub fn as_f64(&self) -> f64 {
25 self.num as f64 / self.den as f64
26 }
27
28 pub fn reduced(mut self) -> Self {
30 if self.den < 0 {
31 self.num = -self.num;
32 self.den = -self.den;
33 }
34 let g = gcd(self.num.unsigned_abs(), self.den.unsigned_abs()) as i64;
35 if g > 1 {
36 self.num /= g;
37 self.den /= g;
38 }
39 self
40 }
41
42 pub fn invert(self) -> Self {
44 Self {
45 num: self.den,
46 den: self.num,
47 }
48 }
49}
50
51impl fmt::Display for Rational {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "{}/{}", self.num, self.den)
54 }
55}
56
57fn gcd(mut a: u64, mut b: u64) -> u64 {
58 while b != 0 {
59 let t = b;
60 b = a % b;
61 a = t;
62 }
63 a.max(1)
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn reduce() {
72 assert_eq!(Rational::new(10, 20).reduced(), Rational::new(1, 2));
73 assert_eq!(Rational::new(-6, 9).reduced(), Rational::new(-2, 3));
74 assert_eq!(Rational::new(6, -9).reduced(), Rational::new(-2, 3));
75 }
76
77 #[test]
78 fn invert() {
79 assert_eq!(Rational::new(1, 2).invert(), Rational::new(2, 1));
80 }
81}