1use core::fmt::{Display, Formatter};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct Ratio {
16 numerator: usize,
17 denominator: usize,
18}
19
20impl Ratio {
21 pub fn new(numerator: usize, denominator: usize) -> Self {
27 assert!(denominator != 0, "ratio denominator must not be zero");
28 let divisor = gcd(numerator, denominator);
29 Self {
30 numerator: numerator / divisor,
31 denominator: denominator / divisor,
32 }
33 }
34
35 pub fn numerator(self) -> usize {
37 self.numerator
38 }
39
40 pub fn denominator(self) -> usize {
42 self.denominator
43 }
44
45 pub fn as_f32(self) -> f32 {
47 self.numerator as f32 / self.denominator as f32
48 }
49
50 pub fn as_f64(self) -> f64 {
52 self.numerator as f64 / self.denominator as f64
53 }
54}
55
56impl Display for Ratio {
57 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
58 write!(f, "{}/{}", self.numerator, self.denominator)
59 }
60}
61
62fn gcd(a: usize, b: usize) -> usize {
63 let (mut a, mut b) = (a, b);
64 while b != 0 {
65 (a, b) = (b, a % b);
66 }
67 a
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use alloc::format;
74
75 #[test]
76 fn reduces_to_lowest_terms() {
77 let r = Ratio::new(2, 4);
78 assert_eq!(r.numerator(), 1);
79 assert_eq!(r.denominator(), 2);
80 }
81
82 #[test]
83 fn equal_fractions_are_equal() {
84 assert_eq!(Ratio::new(1, 2), Ratio::new(2, 4));
85 assert_eq!(Ratio::new(3, 9), Ratio::new(1, 3));
86 }
87
88 #[test]
89 fn zero_numerator_reduces_to_zero_over_one() {
90 let r = Ratio::new(0, 5);
91 assert_eq!(r.numerator(), 0);
92 assert_eq!(r.denominator(), 1);
93 }
94
95 #[test]
96 #[should_panic(expected = "denominator must not be zero")]
97 fn zero_denominator_panics() {
98 Ratio::new(1, 0);
99 }
100
101 #[test]
102 fn conversions_are_accurate() {
103 let r = Ratio::new(1, 4);
104 assert_eq!(r.as_f32(), 0.25);
105 assert_eq!(r.as_f64(), 0.25);
106 }
107
108 #[test]
109 fn display_shows_reduced_form() {
110 let r = Ratio::new(6, 8);
111 assert_eq!(format!("{}", r), "3/4");
112 }
113}