Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rational: Specify invariants, no more negative denominators #210

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 53 additions & 58 deletions rational/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use integer::Integer;
use traits::{FromPrimitive, Float, PrimInt, Num, Signed, Zero, One};

/// Represents the ratio between 2 numbers.
#[derive(Copy, Clone, Hash, Debug)]
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
#[allow(missing_docs)]
pub struct Ratio<T> {
Expand All @@ -56,13 +56,17 @@ pub type Rational64 = Ratio<i64>;
pub type BigRational = Ratio<BigInt>;

impl<T: Clone + Integer> Ratio<T> {
/// Creates a ratio representing the integer `t`.
/// Creates a `Ratio` representing the integer `t`.
#[inline]
pub fn from_integer(t: T) -> Ratio<T> {
Ratio::new_raw(t, One::one())
}

/// Creates a ratio without checking for `denom == 0` or reducing.
/// Creates a `Ratio` in an unchecked fashion.
///
/// The user must ensure that the invariants of `Ratio` are maintained;
/// in particular, `numer` and `denom` share no factor in common;
/// `denom` is strictly positive; and zero is represented as `0/1`.
#[inline]
pub fn new_raw(numer: T, denom: T) -> Ratio<T> {
Ratio {
Expand All @@ -71,7 +75,7 @@ impl<T: Clone + Integer> Ratio<T> {
}
}

/// Create a new Ratio. Fails if `denom == 0`.
/// Creates a new `Ratio`. Fails if `denom == 0`.
#[inline]
pub fn new(numer: T, denom: T) -> Ratio<T> {
if denom == Zero::zero() {
Expand All @@ -82,7 +86,7 @@ impl<T: Clone + Integer> Ratio<T> {
ret
}

/// Converts to an integer.
/// Converts to an integer, rounding towards zero.
#[inline]
pub fn to_integer(&self) -> T {
self.trunc().numer
Expand All @@ -106,7 +110,7 @@ impl<T: Clone + Integer> Ratio<T> {
self.denom == One::one()
}

/// Put self into lowest terms, with denom > 0.
/// Puts self into lowest terms, with denom > 0.
fn reduce(&mut self) {
let g: T = self.numer.gcd(&self.denom);

Expand All @@ -124,17 +128,27 @@ impl<T: Clone + Integer> Ratio<T> {
}
}

/// Returns a `reduce`d copy of self.
/// Returns a reduced copy of self.
///
/// In general, it is not necessary to use this method, as the only
/// method of procuring a non-reduced fraction is through `new_raw`.
pub fn reduced(&self) -> Ratio<T> {
let mut ret = self.clone();
ret.reduce();
ret
}

/// Returns the reciprocal.
///
/// Fails if the `Ratio` is zero.
#[inline]
pub fn recip(&self) -> Ratio<T> {
Ratio::new_raw(self.denom.clone(), self.numer.clone())
match self.numer.cmp(&T::zero()) {
cmp::Ordering::Equal => panic!("numerator == 0"),
cmp::Ordering::Greater => Ratio::new_raw(self.denom.clone(), self.numer.clone()),
cmp::Ordering::Less => Ratio::new_raw(T::zero() - self.denom.clone(),
T::zero() - self.numer.clone())
}
}

/// Rounds towards minus infinity.
Expand Down Expand Up @@ -201,15 +215,17 @@ impl<T: Clone + Integer> Ratio<T> {
Ratio::from_integer(self.numer.clone() / self.denom.clone())
}

/// Returns the fractional part of a number.
/// Returns the fractional part of a number, with division rounded towards zero.
///
/// Satisfies `self == self.trunc() + self.fract()`.
#[inline]
pub fn fract(&self) -> Ratio<T> {
Ratio::new_raw(self.numer.clone() % self.denom.clone(), self.denom.clone())
}
}

impl<T: Clone + Integer + PrimInt> Ratio<T> {
/// Raises the ratio to the power of an exponent
/// Raises the `Ratio` to the power of an exponent.
#[inline]
pub fn pow(&self, expon: i32) -> Ratio<T> {
match expon.cmp(&0) {
Expand Down Expand Up @@ -258,12 +274,7 @@ impl<T: Clone + Integer> Ord for Ratio<T> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
// With equal denominators, the numerators can be directly compared
if self.denom == other.denom {
let ord = self.numer.cmp(&other.numer);
return if self.denom < T::zero() {
ord.reverse()
} else {
ord
};
return self.numer.cmp(&other.numer);
}

// With equal numerators, the denominators can be inversely compared
Expand Down Expand Up @@ -310,16 +321,6 @@ impl<T: Clone + Integer> PartialOrd for Ratio<T> {
}
}

impl<T: Clone + Integer> PartialEq for Ratio<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == cmp::Ordering::Equal
}
}

impl<T: Clone + Integer> Eq for Ratio<T> {}


macro_rules! forward_val_val_binop {
(impl $imp:ident, $method:ident) => {
impl<T: Clone + Integer> $imp<Ratio<T>> for Ratio<T> {
Expand Down Expand Up @@ -497,11 +498,7 @@ impl<T: Clone + Integer> Num for Ratio<T> {
impl<T: Clone + Integer + Signed> Signed for Ratio<T> {
#[inline]
fn abs(&self) -> Ratio<T> {
if self.is_negative() {
-self.clone()
} else {
self.clone()
}
Ratio::new_raw(self.numer.clone().abs(), self.denom.clone())
}

#[inline]
Expand All @@ -515,25 +512,17 @@ impl<T: Clone + Integer + Signed> Signed for Ratio<T> {

#[inline]
fn signum(&self) -> Ratio<T> {
if self.is_positive() {
Self::one()
} else if self.is_zero() {
Self::zero()
} else {
-Self::one()
}
Ratio::from_integer(self.numer.clone().signum())
}

#[inline]
fn is_positive(&self) -> bool {
(self.numer.is_positive() && self.denom.is_positive()) ||
(self.numer.is_negative() && self.denom.is_negative())
self.numer.is_positive()
}

#[inline]
fn is_negative(&self) -> bool {
(self.numer.is_negative() && self.denom.is_positive()) ||
(self.numer.is_positive() && self.denom.is_negative())
self.numer.is_negative()
}
}

Expand Down Expand Up @@ -664,6 +653,10 @@ mod test {
numer: 2,
denom: 1,
};
pub const _NEG2: Rational = Ratio {
numer: -2,
denom: 1,
};
pub const _1_2: Rational = Ratio {
numer: 1,
denom: 2,
Expand All @@ -676,14 +669,6 @@ mod test {
numer: -1,
denom: 2,
};
pub const _1_NEG2: Rational = Ratio {
numer: 1,
denom: -2,
};
pub const _NEG1_NEG2: Rational = Ratio {
numer: -1,
denom: -2,
};
pub const _1_3: Rational = Ratio {
numer: 1,
denom: 3,
Expand Down Expand Up @@ -725,13 +710,15 @@ mod test {

#[test]
fn test_new_reduce() {
let one22 = Ratio::new(2, 2);

assert_eq!(one22, One::one());
assert_eq!(Ratio::new(2, 2), One::one());
assert_eq!(Ratio::new(-2, -2), One::one());
assert_eq!(Ratio::new(0, 5), Zero::zero());
assert_eq!(Ratio::new(0, -5), Zero::zero());
}

#[test]
#[should_panic]
fn test_new_zero() {
fn test_new_denom_zero() {
let _a = Ratio::new(1, 0);
}

Expand Down Expand Up @@ -1002,6 +989,16 @@ mod test {
assert_eq!(_1_2 * _1_2.recip(), _1);
assert_eq!(_3_2 * _3_2.recip(), _1);
assert_eq!(_NEG1_2 * _NEG1_2.recip(), _1);

assert_eq!(_3_2.recip(), _2_3);
assert_eq!(_NEG1_2.recip(), _NEG2);
assert_eq!(_NEG1_2.recip().denom(), &1);
}

#[test]
#[should_panic = "== 0"]
fn test_recip_fail() {
let _a = Ratio::new(0, 1).recip();
}

#[test]
Expand Down Expand Up @@ -1091,15 +1088,13 @@ mod test {
assert_eq!(_1_2.abs_sub(&_3_2), Zero::zero());
assert_eq!(_1_2.signum(), One::one());
assert_eq!(_NEG1_2.signum(), -<Ratio<isize>>::one());
assert_eq!(_2.signum(), One::one());
assert_eq!(_NEG2.signum(), -<Ratio<isize>>::one());
assert_eq!(_0.signum(), Zero::zero());
assert!(_NEG1_2.is_negative());
assert!(_1_NEG2.is_negative());
assert!(!_NEG1_2.is_positive());
assert!(!_1_NEG2.is_positive());
assert!(_1_2.is_positive());
assert!(_NEG1_NEG2.is_positive());
assert!(!_1_2.is_negative());
assert!(!_NEG1_NEG2.is_negative());
assert!(!_0.is_positive());
assert!(!_0.is_negative());
}
Expand Down