Files
Rusty-Fractions/src/lib.rs
2026-04-27 18:38:57 -06:00

297 lines
6.5 KiB
Rust

use std::{fmt, ops};
#[derive(Debug, PartialEq, Eq)]
pub struct Fraction {
num: i64,
den: i64,
}
#[derive(Debug, PartialEq, Eq)]
pub enum FractionError {
DivisionByZero,
ZeroDenominator,
Overflow,
InvalidInteger,
}
impl fmt::Display for FractionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FractionError::DivisionByZero => write!(f, "Division by zero"),
FractionError::ZeroDenominator => write!(f, "Denominator can't be zero"),
FractionError::Overflow => write!(f, "Numeric overflow"),
FractionError::InvalidInteger => write!(f, "Can't convert to integer"),
}
}
}
impl std::error::Error for FractionError {}
impl Fraction {
pub fn new(num: i64, den: i64) -> Result<Self, FractionError> {
if den == 0 {
return Err(FractionError::ZeroDenominator);
}
let mut new = Fraction { num, den };
new.reduce();
new.correct_sign();
Ok(new)
}
pub fn reciprocal(&self) -> Result<Self, FractionError> {
Fraction::new(self.den, self.num)
}
pub fn abs(&self) -> Self {
Fraction {
num: self.num.abs(),
den: self.den,
}
}
pub fn is_zero(&self) -> bool {
self.num == 0
}
pub fn is_integer(&self) -> bool {
self.den == 1
}
fn gcd(a: i64, b: i64) -> i64 {
if a == 0 {
return b;
}
Fraction::gcd(b % a, a)
}
fn reduce(&mut self) {
let gdc = Fraction::gcd(self.num, self.den);
self.num /= gdc;
self.den /= gdc;
}
fn correct_sign(&mut self) {
if self.num < 0 && self.den < 0 {
self.num = self.num.abs();
self.den = self.den.abs();
} else if self.den < 0 {
self.num = -self.num;
self.den = self.den.abs();
}
}
}
impl ops::Mul for Fraction {
type Output = Self;
fn mul(self, other: Self) -> Self::Output {
let mut new = Fraction {
num: self.num * other.num,
den: self.den * other.den,
};
new.reduce();
new
}
}
impl ops::Add for Fraction {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
let mut new = Fraction {
num: (self.num * other.den) + (self.den * other.num),
den: self.den * other.den,
};
new.reduce();
new.correct_sign();
new
}
}
impl ops::Div for Fraction {
type Output = Result<Self, FractionError>;
fn div(self, other: Self) -> Self::Output {
if other.is_zero() {
return Err(FractionError::DivisionByZero);
}
let mut new = Fraction {
num: self.num * other.den,
den: self.den * other.num,
};
new.reduce();
new.correct_sign();
Ok(new)
}
}
impl ops::Sub for Fraction {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
let mut new = Fraction {
num: (self.num * other.den) - (self.den * other.num),
den: self.num * other.den,
};
new.reduce();
new.correct_sign();
new
}
}
impl ops::Neg for Fraction {
type Output = Self;
fn neg(self) -> Self::Output {
Fraction {
num: -self.num,
den: self.den,
}
}
}
impl fmt::Display for Fraction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{0}/{1}", self.num, self.den)
}
}
impl From<i32> for Fraction {
fn from(value: i32) -> Self {
Fraction {
num: value.into(),
den: 1,
}
}
}
impl From<i64> for Fraction {
fn from(value: i64) -> Self {
Fraction { num: value, den: 1 }
}
}
impl TryFrom<(i32, i32)> for Fraction {
type Error = FractionError;
fn try_from(value: (i32, i32)) -> Result<Self, Self::Error> {
if value.1 == 0 {
return Err(FractionError::ZeroDenominator);
}
Ok(Fraction {
num: value.0.into(),
den: value.1.into(),
})
}
}
impl TryFrom<(i64, i64)> for Fraction {
type Error = FractionError;
fn try_from(value: (i64, i64)) -> Result<Self, Self::Error> {
if value.1 == 0 {
return Err(FractionError::ZeroDenominator);
}
Ok(Fraction {
num: value.0,
den: value.1,
})
}
}
impl TryInto<i64> for Fraction {
type Error = FractionError;
fn try_into(self) -> Result<i64, Self::Error> {
if self.den != 1 {
return Err(FractionError::InvalidInteger);
}
Ok(self.num)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_valid_fraction() {
let f = Fraction::new(2, 4).unwrap();
assert_eq!(f.num, 1);
assert_eq!(f.den, 2);
}
#[test]
fn test_new_zero_denominator_error() {
let f = Fraction::new(2, 0);
assert!(matches!(f, Err(FractionError::ZeroDenominator)));
}
#[test]
fn test_new_reduces_fraction() {
let f = Fraction::new(256, 512).unwrap();
assert_eq!(f.num, 1);
assert_eq!(f.den, 2);
}
#[test]
fn test_new_normalizes_signs() {
let f = Fraction::new(1, -2).unwrap();
assert_eq!(f.num, -1);
assert_eq!(f.den, 2);
}
#[test]
fn test_add_basic() {
let a = Fraction::new(7, 13).unwrap();
let b = Fraction::new(4, 7).unwrap();
let c = a + b;
assert_eq!(c.num, 101);
assert_eq!(c.den, 91);
}
#[test]
fn test_add_with_common_denominator() {
let a = Fraction::new(7, 13).unwrap();
let b = Fraction::new(4, 13).unwrap();
let c = a + b;
assert_eq!(c.num, 11);
assert_eq!(c.den, 13);
}
#[test]
fn test_add_positive_and_negative() {
let a = Fraction::new(-32, 237).unwrap();
let b = Fraction::new(22, 44).unwrap();
let c = a + b;
assert_eq!(c.num, 173);
assert_eq!(c.den, 474);
}
#[test]
fn test_add_negative_and_negative() {
let a = Fraction::new(-500, 12).unwrap();
let b = Fraction::new(-22, 4).unwrap();
let c = a + b;
assert_eq!(c.num, -283);
assert_eq!(c.den, 6);
}
#[test]
fn test_add_with_zero() {
let a = Fraction::new(0, 13).unwrap();
let b = Fraction::new(4, 7).unwrap();
let c = a + b;
assert_eq!(c.num, 4);
assert_eq!(c.den, 7);
}
}