1use core::{cmp::Ordering, fmt::Display};
2
3use thiserror::Error;
4
5use crate::common::Fraction;
6
7impl Display for Fraction {
8 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
9 write!(f, "{}/{}", self.numerator, self.denominator)
10 }
11}
12
13#[derive(Debug, Error, PartialEq, Eq, Clone)]
15#[non_exhaustive]
16pub enum FractionError {
17 #[error("Denominator cannot be zero")]
18 ZeroDenominator,
19 #[error("Fraction arithmetic operation resulted in an overflow")]
20 Overflow,
21 #[error("Fraction arithmetic operation resulted in an undefined state")]
22 Undefined,
23}
24
25impl Fraction {
26 #[must_use]
31 #[inline]
32 pub const fn gcd(a: i64, b: i64) -> u64 {
33 let mut ua = a.unsigned_abs();
34 let mut ub = b.unsigned_abs();
35
36 while ub != 0 {
37 let temp = ub;
38 ub = ua % ub;
39 ua = temp;
40 }
41 ua
42 }
43
44 #[inline]
46 pub fn lcm(a: i64, b: i64) -> Result<i128, FractionError> {
47 if a == 0 || b == 0 {
48 return Err(FractionError::ZeroDenominator);
49 }
50 let common_divisor = i128::from(Self::gcd(a, b));
51 let val_a = i128::from(a);
52 let val_b = i128::from(b);
53
54 let term1 = val_a
55 .checked_div(common_divisor)
56 .ok_or(FractionError::Overflow)?;
57 term1
58 .checked_mul(val_b)
59 .ok_or(FractionError::Overflow)
60 }
61
62 #[inline]
65 pub const fn new(numerator: i64, denominator: i64) -> Result<Self, FractionError> {
66 if denominator == 0 {
67 return Err(FractionError::ZeroDenominator);
68 }
69
70 if denominator == i64::MIN {
72 return Err(FractionError::Overflow);
73 }
74 if denominator < 0 && numerator == i64::MIN {
77 return Err(FractionError::Overflow);
78 }
79
80 let (mut num, mut den) = (numerator, denominator);
81
82 if den < 0 {
84 num = -num;
85 den = -den;
86 }
87
88 let common_divisor = Self::gcd(num, den);
89
90 let cd = common_divisor.cast_signed();
94
95 Ok(Self {
96 numerator: num / cd,
97 denominator: den / cd,
98 })
99 }
100
101 #[inline]
108 pub const fn reduce(&mut self) {
109 if self.denominator == 0 {
110 return;
111 }
112
113 if self.denominator == i64::MIN {
116 return;
117 }
118 if self.denominator < 0 && self.numerator == i64::MIN {
119 return;
120 }
121
122 if self.denominator < 0 {
124 self.numerator = -self.numerator;
125 self.denominator = -self.denominator;
126 }
127
128 let common_divisor = Self::gcd(self.numerator, self.denominator);
129
130 let cd = common_divisor.cast_signed();
132
133 self.numerator /= cd;
134 self.denominator /= cd;
135 }
136
137 #[must_use]
139 #[inline]
140 pub const fn reduced(mut self) -> Self {
141 self.reduce();
142 self
143 }
144
145 #[inline]
147 pub fn checked_add(self, other: Self) -> Result<Self, FractionError> {
148 let common_denominator_i128 = Self::lcm(self.denominator, other.denominator)?;
149
150 let factor_self = common_denominator_i128
151 .checked_div(i128::from(self.denominator))
152 .ok_or(FractionError::Overflow)?;
153
154 let factor_other = common_denominator_i128
155 .checked_div(i128::from(other.denominator))
156 .ok_or(FractionError::Overflow)?;
157
158 let new_numerator_left = i128::from(self.numerator)
159 .checked_mul(factor_self)
160 .ok_or(FractionError::Overflow)?;
161
162 let new_numerator_right = i128::from(other.numerator)
163 .checked_mul(factor_other)
164 .ok_or(FractionError::Overflow)?;
165
166 let new_numerator = new_numerator_left
167 .checked_add(new_numerator_right)
168 .ok_or(FractionError::Overflow)?;
169
170 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
171 let den_i64 =
172 i64::try_from(common_denominator_i128).map_err(|_| FractionError::Overflow)?;
173
174 Self::new(num_i64, den_i64)
175 }
176
177 #[inline]
179 pub fn checked_sub(self, other: Self) -> Result<Self, FractionError> {
180 let common_denominator_i128 = Self::lcm(self.denominator, other.denominator)?;
181
182 let factor_self = common_denominator_i128
183 .checked_div(i128::from(self.denominator))
184 .ok_or(FractionError::Overflow)?;
185
186 let factor_other = common_denominator_i128
187 .checked_div(i128::from(other.denominator))
188 .ok_or(FractionError::Overflow)?;
189
190 let new_numerator_left = i128::from(self.numerator)
191 .checked_mul(factor_self)
192 .ok_or(FractionError::Overflow)?;
193
194 let new_numerator_right = i128::from(other.numerator)
195 .checked_mul(factor_other)
196 .ok_or(FractionError::Overflow)?;
197
198 let new_numerator = new_numerator_left
199 .checked_sub(new_numerator_right)
200 .ok_or(FractionError::Overflow)?;
201
202 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
203 let den_i64 =
204 i64::try_from(common_denominator_i128).map_err(|_| FractionError::Overflow)?;
205
206 Self::new(num_i64, den_i64)
207 }
208
209 #[inline]
211 pub fn checked_mul(self, other: Self) -> Result<Self, FractionError> {
212 let new_numerator = i128::from(self.numerator)
213 .checked_mul(i128::from(other.numerator))
214 .ok_or(FractionError::Overflow)?;
215
216 let new_denominator = i128::from(self.denominator)
217 .checked_mul(i128::from(other.denominator))
218 .ok_or(FractionError::Overflow)?;
219
220 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
221 let den_i64 = i64::try_from(new_denominator).map_err(|_| FractionError::Overflow)?;
222
223 Self::new(num_i64, den_i64)
224 }
225
226 #[inline]
228 pub fn checked_div(self, other: Self) -> Result<Self, FractionError> {
229 if other.numerator == 0 {
230 return Err(FractionError::Undefined);
231 }
232
233 let new_numerator = i128::from(self.numerator)
234 .checked_mul(i128::from(other.denominator))
235 .ok_or(FractionError::Overflow)?;
236
237 let new_denominator = i128::from(self.denominator)
238 .checked_mul(i128::from(other.numerator))
239 .ok_or(FractionError::Overflow)?;
240
241 let num_i64 = i64::try_from(new_numerator).map_err(|_| FractionError::Overflow)?;
242 let den_i64 = i64::try_from(new_denominator).map_err(|_| FractionError::Overflow)?;
243
244 Self::new(num_i64, den_i64)
245 }
246
247 #[must_use]
256 #[inline]
257 pub fn to_f64_unchecked(self) -> f64 {
258 self.try_into().unwrap()
259 }
260}
261
262impl PartialOrd for Fraction {
263 #[inline]
264 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
265 if self.denominator <= 0 || other.denominator <= 0 {
266 return None;
267 }
268 let self_val = i128::from(self.numerator) * i128::from(other.denominator);
269 let other_val = i128::from(other.numerator) * i128::from(self.denominator);
270
271 Some(self_val.cmp(&other_val))
272 }
273}
274
275impl TryFrom<Fraction> for f64 {
276 type Error = FractionError;
277 #[inline]
278 fn try_from(fraction: Fraction) -> Result<Self, Self::Error> {
279 if fraction.denominator == 0 {
280 return Err(FractionError::ZeroDenominator);
281 }
282
283 let num_f64 = fraction.numerator as Self;
284 let den_f64 = fraction.denominator as Self;
285
286 Ok(num_f64 / den_f64)
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 fn frac(n: i64, d: i64) -> Result<Fraction, FractionError> {
295 Fraction::new(n, d)
296 }
297
298 #[test]
299 fn test_creation_and_reduction() {
300 let f = frac(2, 4).unwrap();
302 assert_eq!(f.numerator, 1);
303 assert_eq!(f.denominator, 2);
304
305 let f = frac(1, -2).unwrap();
307 assert_eq!(f.numerator, -1);
308 assert_eq!(f.denominator, 2);
309
310 let f = frac(-2, -4).unwrap();
312 assert_eq!(f.numerator, 1);
313 assert_eq!(f.denominator, 2);
314
315 let f = frac(0, 5).unwrap();
317 assert_eq!(f.numerator, 0);
318 assert_eq!(f.denominator, 1); }
320
321 #[test]
322 fn test_creation_edge_cases() {
323 assert_eq!(frac(1, 0), Err(FractionError::ZeroDenominator));
325
326 let f = frac(i64::MIN, 1).unwrap();
328 assert_eq!(f.numerator, i64::MIN);
329 assert_eq!(f.denominator, 1);
330
331 assert_eq!(frac(1, i64::MIN), Err(FractionError::Overflow));
333
334 assert_eq!(frac(i64::MIN, -1), Err(FractionError::Overflow));
336 }
337
338 #[test]
339 fn test_arithmetic_add() {
340 let f1 = frac(1, 2).unwrap();
342 let f2 = frac(1, 3).unwrap();
343 let res = f1.checked_add(f2).unwrap();
344 assert_eq!(res.numerator, 5);
345 assert_eq!(res.denominator, 6);
346
347 let f1 = frac(1, 2).unwrap();
349 let f2 = frac(1, -2).unwrap();
350 let res = f1.checked_add(f2).unwrap();
351 assert_eq!(res.numerator, 0);
352 assert_eq!(res.denominator, 1);
353 }
354
355 #[test]
356 fn test_arithmetic_sub() {
357 let f1 = frac(1, 2).unwrap();
359 let f2 = frac(1, 3).unwrap();
360 let res = f1.checked_sub(f2).unwrap();
361 assert_eq!(res.numerator, 1);
362 assert_eq!(res.denominator, 6);
363 }
364
365 #[test]
366 fn test_arithmetic_mul() {
367 let f1 = frac(2, 3).unwrap();
369 let f2 = frac(3, 4).unwrap();
370 let res = f1.checked_mul(f2).unwrap();
371 assert_eq!(res.numerator, 1);
372 assert_eq!(res.denominator, 2);
373 }
374
375 #[test]
376 fn test_arithmetic_div() {
377 let f1 = frac(1, 2).unwrap();
379 let f2 = frac(1, 2).unwrap();
380 let res = f1.checked_div(f2).unwrap();
381 assert_eq!(res.numerator, 1);
382 assert_eq!(res.denominator, 1);
383
384 let f1 = frac(1, 2).unwrap();
386 let f2 = frac(0, 1).unwrap();
387 assert_eq!(f1.checked_div(f2), Err(FractionError::Undefined));
388 }
389
390 #[test]
391 fn test_ordering() {
392 let f1 = frac(1, 2).unwrap();
393 let f2 = frac(1, 3).unwrap();
394 let f3 = frac(2, 4).unwrap(); assert!(f1 > f2); assert!(f2 < f1);
398 assert_eq!(f1.partial_cmp(&f3), Some(Ordering::Equal));
399
400 let neg = frac(-1, 2).unwrap();
402 assert!(neg < f1);
403 }
404
405 #[test]
406 fn test_f64_conversion() {
407 let f = frac(1, 2).unwrap();
408 let val: f64 = f.try_into().unwrap();
409 assert!((val - 0.5).abs() < f64::EPSILON);
410
411 let bad_frac = Fraction {
413 numerator: 1,
414 denominator: 0,
415 };
416 assert_eq!(f64::try_from(bad_frac), Err(FractionError::ZeroDenominator));
417 }
418
419 #[test]
420 fn test_overflow_checks() {
421 let f1 = frac(i64::MAX - 1, 1).unwrap();
424 let f2 = frac(2, 1).unwrap();
425
426 assert_eq!(f1.checked_add(f2), Err(FractionError::Overflow));
428 }
429
430 #[test]
431 fn test_gcd_edge_cases() {
432 assert_eq!(Fraction::gcd(10, 5), 5);
434 assert_eq!(Fraction::gcd(-10, 5), 5);
435
436 assert_eq!(Fraction::gcd(i64::MIN, 0), 9_223_372_036_854_775_808);
439 }
440
441 #[test]
442 fn test_manual_corruption_resilience() {
443 let mut f = Fraction {
445 numerator: 1,
446 denominator: i64::MIN,
447 };
448 f.reduce();
450 assert_eq!(f.denominator, i64::MIN);
452
453 let mut f2 = Fraction {
455 numerator: i64::MIN,
456 denominator: -1,
457 };
458 f2.reduce();
461 assert_eq!(f2.denominator, -1);
462 }
463
464 #[test]
465 fn test_reduce_works_normally() {
466 let mut f = Fraction {
467 numerator: 2,
468 denominator: 4,
469 };
470 f.reduce();
471 assert_eq!(f.numerator, 1);
472 assert_eq!(f.denominator, 2);
473
474 let mut f2 = Fraction {
475 numerator: 2,
476 denominator: -4,
477 };
478 f2.reduce();
479 assert_eq!(f2.numerator, -1);
480 assert_eq!(f2.denominator, 2);
481 }
482}