Inspired by another question here on Code Review, I decided to try implementing a Fraction
type in Rust.
Requirements:
- Able to be added, subtracted, multiplied, divided
- Able to be compared (equality and ordering)
- Able to be converted to the floating point representation of the fraction
- When printed to screen, automatically simplify the fraction
I created methods for arithmetic, as well as implementing Eq
, PartialEq
, and PartialOrd
. As far as I can tell, I can't implement Ord
itself, as the f64
type cannot be fully ordered. In my implementation for fmt::Display
, I simplify the fraction and remove any '1' denominators from the display.
Ideally I'd place this into a module for use in my other programs, but I haven't wrapped my head around the crate / module system yet.
#![crate_type = "lib"]
use std::fmt;
use std::cmp;
//////////
pub struct Fraction {
numerator: i64,
denominator: i64,
}
impl fmt::Display for Fraction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Reduce, THEN write
let temp: Fraction = self.reduce();
if temp.denominator == 1 {
write!(f, "{}", temp.numerator)
}
else {
write!(f, "{}/{}", temp.numerator, temp.denominator)
}
}
}
impl cmp::PartialEq for Fraction {
fn eq(&self, other: &Fraction) -> bool {
// Simplify both before comparing
let simp_self = self.reduce();
let simp_other = other.reduce();
simp_self.numerator == simp_other.numerator && simp_self.denominator == simp_other.denominator
}
}
impl cmp::Eq for Fraction {}
impl cmp::PartialOrd for Fraction {
fn partial_cmp(&self, other: &Fraction) -> Option<cmp::Ordering> {
self.to_decimal().partial_cmp(&other.to_decimal())
}
}
impl Fraction {
/// Creates a new fraction with the given numerator and denominator
/// Panics if given a denominator of 0
pub fn new(numerator: i64, denominator: i64) -> Fraction {
if denominator == 0 { panic!("Tried to create a fraction with a denominator of 0!") }
if denominator < 0 {
// If the denominator is negative, multiply both by -1
Fraction { numerator: -numerator, denominator: -denominator }
}
else {
Fraction { numerator: numerator, denominator: denominator }
}
}
/// Returns a new Fraction equal to this Fraction plus another
pub fn add<'a>(&self, other: &'a Fraction) -> Fraction {
Fraction { numerator: (self.numerator * other.denominator + other.numerator * self.denominator), denominator: (self.denominator * other.denominator) }
}
/// Returns a new Fraction equal to this Fraction minus another
pub fn subtract<'a>(&self, other: &'a Fraction) -> Fraction {
Fraction { numerator: (self.numerator * other.denominator - other.numerator * self.denominator), denominator: (self.denominator * other.denominator) }
}
/// Returns a new Fraction equal to this Fraction multiplied by another
pub fn multiply<'a>(&self, other: &'a Fraction) -> Fraction {
Fraction { numerator: (self.numerator * other.numerator), denominator: (self.denominator * other.denominator) }
}
/// Returns a new Fraction equal to this Fraction divided by another
pub fn divide<'a>(&self, other: &'a Fraction) -> Fraction {
Fraction { numerator: (self.numerator * other.denominator), denominator: (self.denominator * other.numerator) }
}
/// Returns a new Fraction that is equal to this one, but simplified
pub fn reduce(&self) -> Fraction {
// Divide numerator and denominator by gcd [use absolute value because negatives]
let _gcd = gcd(self.numerator.abs(), self.denominator.abs());
Fraction { numerator: (self.numerator / _gcd) , denominator: (self.denominator / _gcd) }
}
/// Returns a decimal equivalent to this Fraction
pub fn to_decimal(&self) -> f64 {
self.numerator as f64/ self.denominator as f64
}
}
//////////
// Calculate the greatest common denominator for two numbers
pub fn gcd(a: i64, b: i64) -> i64 {
// Terminal cases
if a == b { return a }
if a == 0 { return b }
if b == 0 { return a }
if a % 2 == 0 { // a is even
if b % 2 != 0 { // b is odd
return gcd(a/2, b)
}
else { // a and b are even
return gcd(a/2, b/2) * 2
}
}
// a is odd
if b % 2 == 0 { // b is even
return gcd(a, b/2)
}
// Reduce larger argument
if a > b { return gcd((a - b)/2, b) }
return gcd((b - a)/2, a)
}
#[test]
fn ordering_test() {
let a = Fraction::new(1, 2);
let b = Fraction::new(3, 4);
let c = Fraction::new(4, 3);
let d = Fraction::new(-1, 2);
assert!(a < b);
assert!(a <= b);
assert!(c > b);
assert!(c >= a);
assert!(d < a);
}
#[test]
fn equality_test() {
let a = Fraction::new(1, 2);
let b = Fraction::new(2, 4);
let c = Fraction::new(5, 5);
assert!(a == b);
assert!(a != c);
}
#[test]
fn arithmetic_test() {
let a = Fraction::new(1, 2);
let b = Fraction::new(3, 4);
assert!(a.add(&a) == Fraction::new(1, 1));
assert!(a.subtract(&a) == Fraction::new(0, 5));
assert!(a.multiply(&b) == Fraction::new(3, 8));
assert!(a.divide(&b) == Fraction::new(4, 6));
}