proto_types/common/
fraction.rs1use std::{cmp::Ordering, fmt::Display};
2
3use thiserror::Error;
4
5use crate::common::Fraction;
6
7impl Display for Fraction {
8 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9 write!(f, "{}/{}", self.numerator, self.denominator)
10 }
11}
12
13#[derive(Debug, Error, PartialEq, Eq, Clone)]
15pub enum FractionError {
16 #[error("Denominator cannot be zero")]
17 ZeroDenominator,
18 #[error("Fraction arithmetic operation resulted in an overflow")]
19 Overflow,
20 #[error("Fraction arithmetic operation resulted in an undefined state")]
21 Undefined,
22}
23
24impl Fraction {
25 pub fn gcd(mut a: i64, mut b: i64) -> i64 {
27 while b != 0 {
28 let temp = b;
29 b = a % b;
30 a = temp;
31 }
32 a.abs()
33 }
34
35 pub fn lcm(a: i64, b: i64) -> Result<i128, FractionError> {
37 if a == 0 || b == 0 {
38 return Err(FractionError::ZeroDenominator);
39 }
40 let common_divisor = Self::gcd(a, b) as i128;
41 let val_a = a as i128;
42 let val_b = b as i128;
43
44 let term1 = val_a
45 .checked_div(common_divisor)
46 .ok_or(FractionError::Overflow)?;
47 term1.checked_mul(val_b).ok_or(FractionError::Overflow)
48 }
49
50 pub fn new(numerator: i64, denominator: i64) -> Result<Self, FractionError> {
53 if denominator == 0 {
54 return Err(FractionError::ZeroDenominator);
55 }
56
57 let (mut num, mut den) = (numerator, denominator);
58
59 if den < 0 {
61 num = -num;
62 den = -den;
63 }
64
65 let common_divisor = Self::gcd(num, den);
66 Ok(Fraction {
67 numerator: num / common_divisor,
68 denominator: den / common_divisor,
69 })
70 }
71
72 pub fn reduce(&mut self) {
75 if self.denominator == 0 {
76 return;
77 }
78
79 if self.denominator < 0 {
81 self.numerator = -self.numerator;
82 self.denominator = -self.denominator;
83 }
84
85 let common_divisor = Self::gcd(self.numerator, self.denominator);
86 self.numerator /= common_divisor;
87 self.denominator /= common_divisor;
88 }
89
90 pub fn reduced(mut self) -> Self {
92 self.reduce();
93 self
94 }
95
96 pub fn checked_add(self, other: Self) -> Result<Self, FractionError> {
98 let common_denominator_i128 = Self::lcm(self.denominator, other.denominator)?;
99
100 let factor_self = common_denominator_i128
101 .checked_div(self.denominator as i128)
102 .ok_or(FractionError::Overflow)?;
103
104 let factor_other = common_denominator_i128
105 .checked_div(other.denominator as i128)
106 .ok_or(FractionError::Overflow)?;
107
108 let new_numerator_left = (self.numerator as i128)
109 .checked_mul(factor_self)
110 .ok_or(FractionError::Overflow)?;
111
112 let new_numerator_right = (other.numerator as i128)
113 .checked_mul(factor_other)
114 .ok_or(FractionError::Overflow)?;
115
116 let new_numerator = new_numerator_left
117 .checked_add(new_numerator_right)
118 .ok_or(FractionError::Overflow)?;
119
120 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
121 let den_i64 = i64::try_from(common_denominator_i128).map_err(|_| FractionError::Overflow)?;
122
123 Fraction::new(num_i64, den_i64)
124 }
125
126 pub fn checked_sub(self, other: Self) -> Result<Self, FractionError> {
128 let common_denominator_i128 = Self::lcm(self.denominator, other.denominator)?;
129
130 let factor_self = common_denominator_i128
131 .checked_div(self.denominator as i128)
132 .ok_or(FractionError::Overflow)?;
133
134 let factor_other = common_denominator_i128
135 .checked_div(other.denominator as i128)
136 .ok_or(FractionError::Overflow)?;
137
138 let new_numerator_left = (self.numerator as i128)
139 .checked_mul(factor_self)
140 .ok_or(FractionError::Overflow)?;
141
142 let new_numerator_right = (other.numerator as i128)
143 .checked_mul(factor_other)
144 .ok_or(FractionError::Overflow)?;
145
146 let new_numerator = new_numerator_left
147 .checked_sub(new_numerator_right)
148 .ok_or(FractionError::Overflow)?;
149
150 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
151 let den_i64 = i64::try_from(common_denominator_i128).map_err(|_| FractionError::Overflow)?;
152
153 Fraction::new(num_i64, den_i64)
154 }
155
156 pub fn checked_mul(self, other: Self) -> Result<Self, FractionError> {
158 let new_numerator = (self.numerator as i128)
159 .checked_mul(other.numerator as i128)
160 .ok_or(FractionError::Overflow)?;
161
162 let new_denominator = (self.denominator as i128)
163 .checked_mul(other.denominator as i128)
164 .ok_or(FractionError::Overflow)?;
165
166 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
167 let den_i64 = i64::try_from(new_denominator).map_err(|_| FractionError::Overflow)?;
168
169 Fraction::new(num_i64, den_i64)
170 }
171
172 pub fn checked_div(self, other: Self) -> Result<Self, FractionError> {
174 if other.numerator == 0 {
175 return Err(FractionError::Undefined);
176 }
177
178 let new_numerator = (self.numerator as i128)
179 .checked_mul(other.denominator as i128)
180 .ok_or(FractionError::Overflow)?;
181
182 let new_denominator = (self.denominator as i128)
183 .checked_mul(other.numerator as i128)
184 .ok_or(FractionError::Overflow)?;
185
186 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
187 let den_i64 = i64::try_from(new_denominator).map_err(|_| FractionError::Overflow)?;
188
189 Fraction::new(num_i64, den_i64)
190 }
191
192 pub fn to_f64_unchecked(self) -> f64 {
201 self.try_into().unwrap_or_else(|e| {
203 panic!("Failed to convert Fraction to f64: {:?}", e)
205 })
206 }
207}
208
209impl PartialOrd for Fraction {
210 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
211 if self.denominator <= 0 || other.denominator <= 0 {
212 return None;
213 }
214 let self_val = (self.numerator as i128) * (other.denominator as i128);
215 let other_val = (other.numerator as i128) * (self.denominator as i128);
216
217 Some(self_val.cmp(&other_val))
218 }
219}
220
221impl TryFrom<Fraction> for f64 {
222 type Error = FractionError;
223 fn try_from(fraction: Fraction) -> Result<Self, Self::Error> {
224 if fraction.denominator == 0 {
225 return Err(FractionError::ZeroDenominator);
226 }
227
228 let num_f64 = fraction.numerator as f64;
229 let den_f64 = fraction.denominator as f64;
230
231 Ok(num_f64 / den_f64)
232 }
233}