roc/stdlib/src/frac.rs
2019-10-21 20:13:28 -04:00

940 lines
39 KiB
Rust

use std::cmp::{Eq, Ordering, PartialEq, PartialOrd};
use std::hash::{Hash, Hasher};
use std::ops::{Add, Sub, Mul, Neg};
use std::mem;
use std::fmt;
use std::marker::PhantomData;
pub type Frac = Fraction<Valid>;
#[derive(Clone, Copy)]
pub struct Fraction<T> {
numerator: i64,
/// A positive denominator represents a valid, rational Fraction.
/// A denominator of zero or lower represents an invalid Fraction. All operations
/// on invalid Fractions have undefined behavior, so be careful not to perform
/// operations on them if they get into that state!
///
/// This design optimizes for runtime efficiency of Roc code, with the cost
/// that it makes Frac more error-prone to use outside of Roc.
///
/// * Roc will accept any pattern of bits in this struct, and will type them as
/// Result { ok: Frac, err: DivByZero }. Behind the scenes, a pattern match
/// on this result will map all positive denominators to Ok and all zero or negative
/// denominators to Err, so there is no extra memory cost to this being typed as Result.
/// * Roc's reciprocal function and division operator return Result values as well. Because
/// of the previous point, these operations have no memory overhead either. If the denominator
/// ends up being zero after these operations, that will map to an Err value as normal.
/// * Roc code does not have the expressive power to construct a Frac with a negative
/// denominator, so it is safe to assume within Roc code that this will never happen.
///
/// Putting these together, it becomes efficient for Roc to assume any value of type Frac F a positive
/// denominator, because it cannot be negative, and if it were zero it would have been wrapped in a Result.
///
/// Outside Roc, it is possible to (for example) temporarily have a negative denominator and then later
/// transition to a positive one. If you know about this and are planning for it, that's fine.
/// However, it's important to remember if you pass a negative denominator to Roc, it will map to an Err value.
///
/// The single source of truth for whether the fraction is positive or negative lies with the numerator.
/// This is for two reasons:
///
/// 1. It means the numerator is exactly an i64, so converting from Int -> Frac will never fail or lose precision.
/// 2. It means checking for an error only needs to invovle the denominator. It's possible to ask "is the denominator positive?"
/// and immediately know if the fraction is valid or not.
///
/// Denominator is stored as an i64 because it supports the same range of positive numbers as i64.
/// This prevents the need for conversions between u64.
denominator: i64,
/// The phantom type records whether the Frac is valid.
/// A Frac with a positive denominator is valid, and all others are invalid.
phantom: PhantomData<T>
}
pub struct Valid;
/// Returns a new fraction.
///
/// Panics in non-release builds if given a denominator larger than the
/// largest possible i64.
///
/// Internally, Fraction stores the denominator as an i64 that must be positive.
/// The positivity guarantee allows Fraction to do comparisons faster, because
/// it can avoid accounting for the case where one fraction has both its
/// numerator and denominator negated, and the other has neither negated.
///
/// The denominator cannot be higher than std::i64::MAX because certain operations
/// (e.g. `reciprocal` and `div`) require swapping numerator and denominator,
/// and numerator is an i64.
///
/// A Fraction with a denominator of zero is invalid, and this function returns
/// a Fraction<U> because it is unknown whether the given denominator is zero.
/// To convert to a Fraction<Valid>, see `valid_or` and `valid_or_else`.
pub fn new<U>(numerator: i64, denominator: u64) -> Fraction<U> {
assert!(denominator <= std::i64::MAX as u64);
Fraction { numerator, denominator: denominator as i64, phantom: PhantomData }
}
/// Returns a new fraction. This is `unsafe` because it assumes, without checking,
/// that it was given a nonzero denominator. Never pass this a zero denominator!
///
/// Panics in non-release builds if given a denominator of zero, or if
/// the denominator is larger than the largest possible i64.
///
/// Internally, Fraction stores the denominator as an i64 that must be positive.
/// The positivity guarantee allows Fraction to do comparisons faster, because
/// it can avoid accounting for the case where one fraction has both its
/// numerator and denominator negated, and the other has neither negated.
///
/// The denominator cannot be higher than std::i64::MAX because certain operations
/// (e.g. `reciprocal` and `div`) require swapping numerator and denominator,
/// and numerator is an i64.
///
/// A Fraction with a denominator of zero is invalid, and this function returns
/// a Fraction<Valid>, so it is important that this function never return a Fraction<Valid>
/// with a zero denominator. That would lead to undefined behavior!
pub unsafe fn unchecked_new(numerator: i64, denominator: u64) -> Fraction<Valid> {
assert_ne!(denominator, 0);
assert!(denominator <= std::i64::MAX as u64);
Fraction { numerator, denominator: denominator as i64, phantom: PhantomData }
}
impl<T> Fraction<T> {
#[inline]
/// Reduces the fraction in place.
pub fn reduced(&self) -> Self {
let common_divisor = gcd(self.numerator, self.denominator);
let numerator = self.numerator / common_divisor;
let denominator = self.denominator / common_divisor;
Fraction { numerator, denominator, phantom: PhantomData }
}
pub fn is_rational(&self) -> bool {
self.denominator.is_positive()
}
#[inline]
/// Reduces the fraction, then returns true iff the denominator is 1.
pub fn is_integer(&self) -> bool {
let common_divisor = gcd(self.numerator, self.denominator);
let denominator = self.denominator / common_divisor;
denominator == 1
}
#[inline]
/// Returns true iff the numerator is zero, without reducing first.
/// This is more efficient than getting the reduced numerator and then
/// comparing it to 0, because that has to execute the reduce operation.
pub fn is_zero(self) -> bool {
self.numerator == 0
}
pub fn abs(&self) -> Self {
match self.numerator.overflowing_abs() {
(numerator, false) =>
Fraction { numerator, denominator: self.denominator, phantom: PhantomData },
(_, true) => {
// We underflowed, so reduce and try again.
let reduced_self = self.reduced();
Fraction {
numerator: reduced_self.numerator.abs(),
denominator: reduced_self.denominator,
phantom: PhantomData
}
}
}
}
pub fn checked_abs(&self) -> Option<Self> {
match self.numerator.overflowing_abs() {
(numerator, false) =>
Some(Fraction { numerator, denominator: self.denominator, phantom: PhantomData }),
(_, true) => {
// We underflowed, so reduce and try again.
let reduced_self = self.reduced();
match reduced_self.numerator.overflowing_abs() {
(numerator, false) => {
Some(Fraction { numerator, denominator: reduced_self.denominator, phantom: PhantomData })
},
(_, true) => None
}
}
}
}
pub fn overflowing_abs(&self) -> (Self, bool) {
match self.numerator.overflowing_abs() {
(numerator, false) =>
(Fraction { numerator, denominator: self.denominator, phantom: PhantomData }, false),
(_, true) => {
let reduced_self = self.reduced();
let (numerator, underflowed) = reduced_self.numerator.overflowing_abs();
(Fraction { numerator, denominator: reduced_self.denominator, phantom: PhantomData }, underflowed)
}
}
}
/// Add two fractions, returning None on overflow. Note: overflow can occur on more than just large numerators!
pub fn checked_add(&self, other: &Self) -> Option<Self> {
if self.denominator == other.denominator {
// Happy path - we get to skip calculating a common denominator!
match self.numerator.overflowing_add(other.numerator) {
(numerator, false) => Some(Fraction { numerator, denominator: self.denominator, phantom: PhantomData }),
(_, true) => self.reducing_checked_add(other)
}
} else {
let common_denom: i64 = self.denominator * other.denominator;
match (common_denom / self.denominator).overflowing_mul(self.numerator) {
(self_numer, false) => {
match (common_denom / other.denominator).overflowing_mul(other.numerator) {
(other_numer, false) => {
match self_numer.overflowing_add(other_numer) {
(numerator, false) =>
Some(Fraction { numerator, denominator: common_denom, phantom: PhantomData }),
(_, true) =>
None
}
}
// Denominator overflowed - try reducing the inputs first.
(_, true) => self.reducing_checked_add(other)
}
},
// Numerator overflowed - try reducing the inputs first.
(_, true) => self.reducing_checked_add(other)
}
}
}
/// Add while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_add(&self, other: &Self) -> Self {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
let denominator = lcm(reduced_self.denominator, reduced_other.denominator);
let numerator =
(reduced_self.numerator * (denominator / reduced_self.denominator))
+ (reduced_other.numerator * (denominator / reduced_other.denominator));
Fraction { numerator, denominator, phantom: PhantomData }
}
/// Add while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_checked_add(&self, other: &Self) -> Option<Self> {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
let denominator = lcm(reduced_self.denominator, reduced_other.denominator);
match (denominator / reduced_self.denominator).overflowing_mul(reduced_self.numerator) {
(self_numer, false) => {
match (denominator / reduced_other.denominator).overflowing_mul(reduced_other.numerator) {
(other_numer, false) => {
match self_numer.overflowing_add(other_numer) {
(numerator, false) =>
Some(Fraction { numerator, denominator, phantom: PhantomData }),
(_, true) =>
None
}
},
(_, true) => None
}
},
(_, true) => None
}
}
/// Subtract two fractions, returning None on overflow. Note: overflow can occur on more than just large numerators!
pub fn checked_sub(&self, other: &Self) -> Option<Self> {
if self.denominator == other.denominator {
// Happy path - we get to skip calculating a common denominator!
match self.numerator.overflowing_sub(other.numerator) {
(numerator, false) => Some(Fraction { numerator, denominator: self.denominator, phantom: PhantomData }),
(_, true) => self.reducing_checked_sub(other)
}
} else {
let common_denom: i64 = self.denominator * other.denominator;
match (common_denom / self.denominator).overflowing_mul(self.numerator) {
(self_numer, false) => {
match (common_denom / other.denominator).overflowing_mul(other.numerator) {
(other_numer, false) => {
match self_numer.overflowing_sub(other_numer) {
(numerator, false) =>
Some(Fraction { numerator, denominator: common_denom, phantom: PhantomData }),
(_, true) =>
None
}
}
// Denominator overflowed - try reducing the inputs first.
(_, true) => self.reducing_checked_sub(other)
}
},
// Numerator overflowed - try reducing the inputs first.
(_, true) => self.reducing_checked_sub(other)
}
}
}
/// Subtract while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_sub(&self, other: &Self) -> Self {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
let denominator = lcm(reduced_self.denominator, reduced_other.denominator);
let numerator =
(reduced_self.numerator * (denominator / reduced_self.denominator))
+ (reduced_other.numerator * (denominator / reduced_other.denominator));
Fraction { numerator, denominator, phantom: PhantomData }
}
/// Subtract while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_checked_sub(&self, other: &Self) -> Option<Self> {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
let denominator = lcm(reduced_self.denominator, reduced_other.denominator);
match (denominator / reduced_self.denominator).overflowing_mul(reduced_self.numerator) {
(self_numer, false) => {
match (denominator / reduced_other.denominator).overflowing_mul(reduced_other.numerator) {
(other_numer, false) => {
match self_numer.overflowing_sub(other_numer) {
(numerator, false) =>
Some(Fraction { numerator, denominator, phantom: PhantomData }),
(_, true) =>
None
}
},
(_, true) => None
}
},
(_, true) => None
}
}
/// Multiply two fractions, returning None on overflow. Note: overflow can occur on more than just large numerators!
pub fn checked_mul(&self, other: &Self) -> Option<Self> {
match self.numerator.checked_mul(other.numerator) {
Some(numerator) => {
// Common denominator is valuable. If we have it, try to preserve it!
if self.denominator == other.denominator
// See if the denominator is evenly divisible by the new numerator.
// If it is, we can "pre-reduce" to the original denominator!
&& numerator.checked_rem(self.denominator)
.map(|rem| rem == 0)
.unwrap_or(false)
{
// TODO There's probably an optimization opportunity here. Check the
// generated instructions - there might be a way to use a division-with-remainder
// instruction, grab the remainder value out of the register, then
// do the test, and if it passes, grab the existing result of the division
// out of the other register without issuing a second division instruction.
Some(Fraction {
numerator: numerator / self.denominator,
denominator: self.denominator,
phantom: PhantomData
})
} else {
match self.denominator.checked_mul(other.denominator) {
Some(denominator) => Some(Fraction { numerator, denominator, phantom: PhantomData }),
// Denominator overflowed. See if reducing the inputs helps!
None => self.reducing_checked_mul(other)
}
}
},
// Numerator overflowed. See if reducing the inputs helps!
None => self.reducing_checked_mul(other)
}
}
/// Return the fracion with numerator and denominator swapped.
///
/// Returns a Fraction with an unbound type variable because this can make it invalid;
/// if the numerator was 0 before (which is valid), now the denominator will be 0 (which is invalid).
pub fn reciprocal<V>(&self) -> Fraction<V> {
let denominator = self.numerator;
let numerator = self.denominator;
// Make sure we don't end up with a negative denominator!
if denominator.is_negative() {
Fraction { numerator: -numerator, denominator: -denominator, phantom: PhantomData }
} else {
Fraction { numerator, denominator, phantom: PhantomData }
}
}
pub fn checked_div<U, V>(&self, other: &Self) -> Option<Fraction<V>> {
// We're going to multiply by the reciprocal of `other`, so if its numerator
// was 0, then the resulting fraction will have 0 for a denominator, so we're done.
if other.numerator == 0 {
return None;
}
match self.numerator.overflowing_mul(other.denominator) {
(numerator, false) => {
match self.denominator.overflowing_mul(other.numerator) {
(denominator, false) => {
// Make sure we don't end up with a negative denominator!
if denominator.is_negative() {
Some(Fraction { numerator: -numerator, denominator: -denominator, phantom: PhantomData })
} else {
Some(Fraction { numerator, denominator, phantom: PhantomData })
}
},
// Denominator overflowed. See if reducing the inputs helps!
(_, true) => self.reducing_checked_div(other)
}
},
// Numerator overflowed. See if reducing the inputs helps!
(_, true) => self.reducing_checked_div(other)
}
}
/// Returns whether the fraction is valid.
/// For a Frac<Valid>, this will always return true.
#[inline(always)]
pub fn is_valid(&self) -> bool {
self.denominator.is_positive()
}
/// If the fraction is valid, return it wrapped in Some.
/// Otherwise, return None.
pub fn into_valid<V>(self) -> Option<Fraction<Valid>> {
if self.is_valid() {
Some(Fraction {numerator: self.numerator, denominator: self.denominator, phantom: PhantomData})
} else {
None
}
}
/// If the fraction is valid, return it with the type variable set accordingly.
/// Otherwise, return the fallback value.
pub fn valid_or<V>(self, fallback: V) -> Result<Fraction<Valid>, V> {
if self.is_valid() {
Ok(Fraction {numerator: self.numerator, denominator: self.denominator, phantom: PhantomData})
} else {
Err(fallback)
}
}
pub fn valid_or_else<F, V>(self, fallback_fn: F) -> Result<Fraction<Valid>, V>
where F: Fn() -> V
{
if self.is_valid() {
Ok(Fraction {numerator: self.numerator, denominator: self.denominator, phantom: PhantomData})
} else {
Err(fallback_fn())
}
}
/// Divide two fractions.
///
/// This returns a Frac with an unbound type parameter because the result may not be valid.
pub fn div<U, V>(&self, other: &Fraction<U>) -> Fraction<V> {
match self.numerator.checked_mul(other.denominator) {
Some(numerator) => {
match self.denominator.checked_mul(other.numerator) {
Some(denominator) => {
// Make sure we don't end up with a negative denominator!
if denominator.is_negative() {
Fraction { numerator: -numerator, denominator: -denominator, phantom: PhantomData }
} else {
Fraction { numerator, denominator, phantom: PhantomData }
}
},
// Denominator overflowed. See if reducing the inputs helps!
None => self.reducing_div(other)
}
},
// Numerator overflowed. See if reducing the inputs helps!
None => self.reducing_div(other)
}
}
/// Multiply while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_mul(&self, other: &Self) -> Self {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
// Preserving common denominator is out the window at this point.
let numerator = reduced_self.numerator * reduced_other.numerator;
let denominator = reduced_self.denominator * reduced_other.denominator;
Fraction { numerator, denominator, phantom: PhantomData }
}
/// Multiply while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_checked_mul(&self, other: &Self) -> Option<Self> {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
// Preserving common denominator is out the window at this point.
match reduced_self.numerator.overflowing_mul(reduced_other.numerator) {
(numerator, false) => {
match reduced_self.denominator.overflowing_mul(reduced_other.denominator) {
(denominator, false) =>
Some(Fraction { numerator, denominator, phantom: PhantomData }),
(_, true) =>
None
}
},
(_, true) => None
}
}
/// Divide while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_checked_div<U, V>(&self, other: &Fraction<U>) -> Option<Fraction<V>> {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
// Preserving common denominator is out the window at this point.
match reduced_self.numerator.overflowing_mul(reduced_other.numerator) {
(denominator, false) => {
match reduced_self.denominator.overflowing_mul(reduced_other.denominator) {
(numerator, false) =>
Some(Fraction { numerator, denominator, phantom: PhantomData }),
(_, true) =>
None
}
},
(_, true) => None
}
}
/// Divide while sacrificing performance to avoid overflow.
/// This should only be used as a fallback after an overflow was caught in a higher-perf arithmetic operation.
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_div<U, V>(&self, other: &Fraction<U>) -> Fraction<V> {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
let numerator = reduced_self.denominator * reduced_other.denominator;
let denominator = reduced_self.numerator * reduced_other.numerator;
Fraction { numerator, denominator, phantom: PhantomData }
}
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_eq(&self, other: &Self) -> bool {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
let denominator = lcm(reduced_self.denominator, reduced_other.denominator);
let self_numerator = reduced_self.numerator * (denominator / reduced_self.denominator);
let other_numerator = reduced_other.numerator * (denominator / reduced_other.denominator);
self_numerator == other_numerator
}
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
fn reducing_cmp(&self, other: &Self) -> Ordering {
let reduced_self = self.reduced();
let reduced_other = other.reduced();
let denominator = lcm(reduced_self.denominator, reduced_other.denominator);
let self_numerator = reduced_self.numerator * (denominator / reduced_self.denominator);
let other_numerator = reduced_other.numerator * (denominator / reduced_other.denominator);
self_numerator.cmp(&other_numerator)
}
#[inline(always)]
pub fn numerator(&self) -> i64 {
self.numerator
}
#[inline(always)]
pub fn denominator(&self) -> i64 {
self.denominator
}
}
impl<T> fmt::Debug for Fraction<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.numerator, self.denominator)
}
}
impl<T> PartialEq for Fraction<T> {
fn eq(&self, other: &Self) -> bool {
if self.denominator == other.denominator {
self.numerator.eq(&other.numerator)
} else if self.numerator == 0 {
// If numerator is 0, the whole fraction is 0.
other.numerator == 0
} else if other.numerator == 0 {
// We couldn't have reached this branch if self.numerator == 0
false
} else {
match self.denominator.overflowing_mul(other.denominator) {
(common_denom, false) => {
match (common_denom / self.denominator).overflowing_mul(self.numerator) {
(self_numer, false) => {
match (common_denom / other.denominator).overflowing_mul(other.numerator) {
(other_numer, false) => self_numer.eq(&other_numer),
// other.numerator overflowed - try reducing the inputs first.
(_, true) => self.reducing_eq(other)
}
},
// self.numerator overflowed - try reducing the inputs first.
(_, true) => self.reducing_eq(other)
}
}
// Common denominator overflowed - try reducing the inputs first.
(_, true) => self.reducing_eq(other)
}
}
}
}
impl<T> Eq for Fraction<T> {}
/// We only have Ord for valid Fracs because potentially invalid ones are essentially equivalent
/// to Result<Frac, ()>. Defining Ord for that case too would mean all Ord implementations would
/// have to do extra checking for situations where either denominator is 0, which does not
/// seem worth the cost.
impl PartialOrd for Fraction<Valid> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Fraction<Valid> {
fn cmp(&self, other: &Self) -> Ordering {
if self.denominator == other.denominator {
self.numerator.cmp(&other.numerator)
} else if self.numerator == 0 {
// If numerator is 0, the whole fraction is 0.
// Just compare numerators to see if the other one is 0, positive, or negative.
0.cmp(&other.numerator)
} else if other.numerator == 0 {
self.numerator.cmp(&0)
} else {
match self.denominator.overflowing_mul(other.denominator) {
(common_denom, false) => {
match (common_denom / self.denominator).overflowing_mul(self.numerator) {
(self_numer, false) => {
match (common_denom / other.denominator).overflowing_mul(other.numerator) {
(other_numer, false) => self_numer.cmp(&other_numer),
// other.numerator overflowed - try reducing the inputs first.
(_, true) => self.reducing_cmp(other)
}
},
// self.numerator overflowed - try reducing the inputs first.
(_, true) => self.reducing_cmp(other)
}
}
// Common denominator overflowed - try reducing the inputs first.
(_, true) => self.reducing_cmp(other)
}
}
}
}
impl Add for Fraction<Valid> {
type Output = Fraction<Valid>;
/// Add two fractions.
fn add(self, other: Self) -> Self {
if self.denominator == other.denominator {
// Happy path - we get to skip calculating a common denominator!
match self.numerator.overflowing_add(other.numerator) {
(numerator, false) => Fraction { numerator, denominator: self.denominator, phantom: PhantomData },
(_, true) => self.reducing_add(&other)
}
} else {
let common_denom: i64 = self.denominator * other.denominator;
// This code would look nicer with checked_ instead of overflowing_, but
// seems likely the perf would be worse.
match (common_denom / self.denominator).overflowing_mul(self.numerator) {
(self_numer, false) => {
match (common_denom / other.denominator).overflowing_mul(other.numerator) {
(other_numer, false) => {
match self_numer.overflowing_add(other_numer) {
(numerator, false) => Fraction { numerator, denominator: common_denom, phantom: PhantomData },
(_, true) => self.reducing_add(&other)
}
},
(_, true) => self.reducing_add(&other)
}
},
(_, true) => self.reducing_add(&other)
}
}
}
}
impl Mul for Fraction<Valid> {
type Output = Fraction<Valid>;
/// Multiply two fractions.
fn mul(self, other: Self) -> Self {
match self.numerator.overflowing_mul(other.numerator) {
(numerator, false) => {
// Common denominator is valuable. If we have it, try to preserve it!
if self.denominator == other.denominator
// See if the denominator is evenly divisible by the new numerator.
// If it is, we can "pre-reduce" to the original denominator!
&& (numerator.overflowing_rem(self.denominator) == (0, false))
{
// TODO There's probably an optimization opportunity here. Check the
// generated instructions - there might be a way to use a division-with-remainder
// instruction, grab the remainder value out of the register, then
// do the test, and if it passes, grab the existing result of the division
// out of the other register without issuing a second division instruction.
Fraction {
numerator: numerator / self.denominator,
denominator: self.denominator,
phantom: PhantomData
}
} else {
match self.denominator.overflowing_mul(other.denominator) {
(denominator, false) => Fraction { numerator, denominator, phantom: PhantomData },
// Denominator overflowed. See if reducing the inputs helps!
(_, true) => self.reducing_mul(&other)
}
}
},
// Numerator overflowed. See if reducing the inputs helps!
(_, true) => self.reducing_mul(&other)
}
}
}
impl Sub for Fraction<Valid> {
type Output = Fraction<Valid>;
/// Subtract two fractions.
fn sub(self, other: Self) -> Self {
if self.denominator == other.denominator {
// Happy path - we get to skip calculating a common denominator!
match self.numerator.overflowing_sub(other.numerator) {
(numerator, false) => Fraction { numerator, denominator: self.denominator, phantom: PhantomData },
(_, true) => self.reducing_sub(&other)
}
} else {
let common_denom: i64 = self.denominator * other.denominator;
// This code would look nicer with checked_ instead of overflowing_, but
// seems likely the perf would be worse.
match (common_denom / self.denominator).overflowing_mul(self.numerator) {
(self_numer, false) => {
match (common_denom / other.denominator).overflowing_mul(other.numerator) {
(other_numer, false) => {
match self_numer.overflowing_sub(other_numer) {
(numerator, false) => Fraction { numerator, denominator: common_denom, phantom: PhantomData },
(_, true) => self.reducing_sub(&other)
}
},
(_, true) => self.reducing_sub(&other)
}
},
(_, true) => self.reducing_sub(&other)
}
}
}
}
impl Hash for Fraction<Valid> {
fn hash<H: Hasher>(&self, state: &mut H) {
let reduced_self = self.reduced();
reduced_self.numerator.hash(state);
reduced_self.denominator.hash(state);
}
}
impl<T> Neg for Fraction<T> {
type Output = Fraction<T>;
fn neg(self) -> Self {
Fraction { numerator: -self.numerator, denominator: self.denominator, phantom: PhantomData }
}
}
impl<T> From<u8> for Fraction<T> {
fn from (numerator: u8) -> Fraction<T> {
Fraction { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
}
}
impl<T> From<i8> for Fraction<T> {
fn from (numerator: i8) -> Fraction<T> {
Fraction { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
}
}
impl<T> From<u16> for Fraction<T> {
fn from (numerator: u16) -> Fraction<T> {
Fraction { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
}
}
impl<T> From<i16> for Fraction<T> {
fn from (numerator: i16) -> Fraction<T> {
Fraction { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
}
}
impl<T> From<u32> for Fraction<T> {
fn from (numerator: u32) -> Fraction<T> {
Fraction { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
}
}
impl<T> From<i32> for Fraction<T> {
fn from (numerator: i32) -> Fraction<T> {
Fraction { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
}
}
impl<T> From<i64> for Fraction<T> {
fn from (numerator: i64) -> Fraction<T> {
Fraction { numerator, denominator: 1, phantom: PhantomData }
}
}
/// This function was adapted from v0.1.39 of the num-integer crate. Licensed under the
/// Apache License, version 2.0. A full copy of the License can be found here:
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// The original code can be found at:
/// https://docs.rs/num-integer/0.1.41/src/num_integer/lib.rs.html#456-500
///
///
/// Calculates the Greatest Common Divisor (GCD) of the number and
/// `other`. The result is always positive.
#[inline]
fn gcd(me: i64, other: i64) -> i64 {
// Use Stein's algorithm
let mut m = me;
let mut n = other;
if m == 0 || n == 0 { return (m | n).abs() }
// find common factors of 2
let shift = (m | n).trailing_zeros();
// The algorithm needs positive numbers, but the minimum value
// can't be represented as a positive one.
// It's also a power of two, so the gcd can be
// calculated by bitshifting in that case
// Assuming two's complement, the number created by the shift
// is positive for all numbers except gcd = abs(min value)
// The call to .abs() causes a panic in debug mode
if m == i64::min_value() || n == i64::min_value() {
return ((1 << shift) as i64).abs()
}
// guaranteed to be positive now, rest like unsigned algorithm
m = m.abs();
n = n.abs();
// divide n and m by 2 until odd
// m inside loop
n >>= n.trailing_zeros();
while m != 0 {
m >>= m.trailing_zeros();
if n > m { mem::swap(&mut n, &mut m) }
m -= n;
}
n << shift
}
/// Lowest common multiple
fn lcm(me: i64, other: i64) -> i64 {
me * (other / gcd(me, other))
}
#[cfg(test)]
mod test_fast_fraction {
use super::Frac;
pub fn frac(numerator: i64, denominator: u64) -> Frac {
super::new(numerator, denominator)
}
#[test]
fn one_plus_one() {
assert_eq!(
frac(1, 1) + frac(1, 1),
frac(2, 1)
);
}
#[test]
fn point_one_plus_point_two() {
assert_eq!(
frac(1, 10) + frac(2, 10),
frac(3, 10)
);
}
#[test]
fn one_minus_one() {
assert_eq!(
frac(1, 1) - frac(1, 1),
frac(0, 9999)
);
}
#[test]
fn multiply() {
assert_eq!(
frac(2, 3) * frac(5, 7),
frac(10, 21)
);
}
#[test]
fn divide() {
assert_eq!(
frac(2, 3).div(&frac(5, 7)),
frac(14, 15)
);
}
}