proto_types/common/
fraction.rs

1use 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/// Errors that can occur during the creation, conversion or validation of a [`Fraction`].
14#[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  /// Helper to calculate Greatest Common Divisor (GCD)
26  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  /// Helper to calculate Least Common Multiple (LCM)
36  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  /// Creates a new Fraction, ensuring the denominator is positive
51  /// and the fraction is reduced to its simplest form.
52  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    // Ensure denominator is positive, sign is carried by numerator
60    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  /// Reduces the fraction to its simplest form by dividing
73  /// numerator and denominator by their greatest common divisor.
74  pub fn reduce(&mut self) {
75    if self.denominator == 0 {
76      return;
77    }
78
79    // Ensure denominator is positive, sign is carried by numerator
80    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  /// Returns a new, reduced Fraction.
91  pub fn reduced(mut self) -> Self {
92    self.reduce();
93    self
94  }
95
96  /// Checked addition for [`Fraction`]s.
97  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  /// Checked subtraction for [`Fraction`]s.
127  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  /// Checked multiplication for [`Fraction`]s.
157  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  /// Checked division for [`Fraction`]s.
173  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  /// Converts the fraction to an `f64`.
193  ///
194  /// # Panics
195  /// Panics if the denominator is zero. This should not happen for [`Fraction`]
196  /// instances created via [`Fraction::new()`] or other checked arithmetic,
197  /// but can occur if a [`Fraction`] is constructed directly in an invalid state.
198  ///
199  /// For a fallible conversion that returns a `Result`, use `TryFrom<Fraction> for f64`.
200  pub fn to_f64_unchecked(self) -> f64 {
201    // We can directly call the TryFrom implementation
202    self.try_into().unwrap_or_else(|e| {
203      // If you want a more specific panic message:
204      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}