mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +00:00
Add some stdlib stuff
This commit is contained in:
parent
f4788b9079
commit
5da8a29015
3 changed files with 678 additions and 503 deletions
|
@ -5,38 +5,78 @@ use std::ops::Neg;
|
|||
use std::mem;
|
||||
use std::fmt;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Fraction {
|
||||
pub struct Frac<T> {
|
||||
numerator: i64,
|
||||
denominator: 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: DivisionByZero }. 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>
|
||||
}
|
||||
|
||||
impl Fraction {
|
||||
/// Returns a new Fraction unless the given denominator was zero,
|
||||
/// in which case returns None.
|
||||
pub struct Valid;
|
||||
|
||||
/// Returns a new Fraction where the denominator is guaranteed to be non-negative.
|
||||
///
|
||||
/// For a more efficient (but astronomically less safe) alternative,
|
||||
/// see new_prereduced_with_positive_denominator.
|
||||
pub fn new(numerator: i64, denominator: i64) -> Option<Fraction> {
|
||||
/// If the provided denominator is negative, both numerator and denominator will
|
||||
/// be negated; this will result in a mathematically equivalent fraction, but with the
|
||||
/// denominator becoming non-negative.
|
||||
///
|
||||
/// All other Fraction operations assume a non-negative denominator, so this is an
|
||||
/// important invariant to maintain!
|
||||
///
|
||||
/// Panics in non-release builds if given a denominator of zero.
|
||||
pub fn new(numerator: i64, denominator: i64) -> Frac<Valid> {
|
||||
assert_ne!(denominator, 0);
|
||||
|
||||
if denominator.is_positive() {
|
||||
Some(Fraction { numerator, denominator })
|
||||
} else if denominator == 0 {
|
||||
None
|
||||
Frac { numerator, denominator, phantom: PhantomData }
|
||||
} else {
|
||||
// Denominator may never be negative. This lets us avoid -0,
|
||||
// get the sign merely by returning the numerator's sign, etc.
|
||||
Some(Fraction { numerator: -numerator, denominator: -denominator })
|
||||
Frac { numerator: -numerator, denominator: -denominator, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
/// Doesn't do any checks in release builds, but panics in debug builds if
|
||||
/// the denominator is not positive.
|
||||
#[inline]
|
||||
pub fn new_from_positive_denominator(numerator: i64, denominator: i64) -> Fraction {
|
||||
assert!(denominator > 0);
|
||||
|
||||
Fraction { numerator, denominator }
|
||||
}
|
||||
impl<T> Frac<T> {
|
||||
|
||||
#[inline]
|
||||
/// Reduces the fraction in place.
|
||||
|
@ -61,6 +101,10 @@ impl Fraction {
|
|||
self.denominator
|
||||
}
|
||||
|
||||
pub fn is_rational(&self) -> bool {
|
||||
self.denominator.is_positive()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Reduces the fraction, then returns a tuple of (numerator, denominator).
|
||||
pub fn reduced(&mut self) -> ( i64, i64 ) {
|
||||
|
@ -83,6 +127,53 @@ impl Fraction {
|
|||
self.numerator == 0
|
||||
}
|
||||
|
||||
pub fn abs(&mut self) -> Self {
|
||||
let (mut numerator, underflowed) = self.numerator.overflowing_abs();
|
||||
|
||||
// If we underflowed, reduce and try again.
|
||||
if underflowed {
|
||||
self.reduce();
|
||||
|
||||
numerator = self.numerator.abs();
|
||||
}
|
||||
|
||||
Frac { numerator, denominator: self.denominator, phantom: PhantomData }
|
||||
}
|
||||
|
||||
|
||||
pub fn checked_abs(&mut self) -> Option<Self> {
|
||||
let (numerator, underflowed) = self.numerator.overflowing_abs();
|
||||
|
||||
// If we underflowed, reduce and try again.
|
||||
if underflowed {
|
||||
self.reduce();
|
||||
|
||||
match self.numerator.overflowing_abs() {
|
||||
(numerator, false) => {
|
||||
Some(Frac { numerator, denominator: self.denominator, phantom: PhantomData })
|
||||
},
|
||||
(_, true) => None
|
||||
}
|
||||
} else {
|
||||
Some(Frac { numerator, denominator: self.denominator, phantom: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overflowing_abs(&mut self) -> (Self, bool) {
|
||||
let (numerator, underflowed) = self.numerator.overflowing_abs();
|
||||
|
||||
// If we underflowed, reduce and try again.
|
||||
if underflowed {
|
||||
self.reduce();
|
||||
|
||||
let (numerator, underflowed) = self.numerator.overflowing_abs();
|
||||
|
||||
(Frac { numerator, denominator: self.denominator, phantom: PhantomData }, underflowed)
|
||||
} else {
|
||||
(Frac { numerator, denominator: self.denominator, phantom: PhantomData }, underflowed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add two fractions, returning None on overflow. Note: overflow can occur on more than just large numerators!
|
||||
/// This requires mutable references because it may decide to reduce
|
||||
/// the fractions mid-operation, and that reduction should persist to avoid having to redo
|
||||
|
@ -90,9 +181,9 @@ impl Fraction {
|
|||
pub fn checked_add(&mut self, other: &mut Self) -> Option<Self> {
|
||||
if self.denominator == other.denominator {
|
||||
// Happy path - we get to skip calculating a common denominator!
|
||||
match self.numerator.checked_add(other.numerator) {
|
||||
Some(numerator) => Some(Fraction { numerator, denominator: self.denominator }),
|
||||
None => self.reducing_checked_add(other)
|
||||
match self.numerator.overflowing_add(other.numerator) {
|
||||
(numerator, false) => Some(Frac { numerator, denominator: self.denominator, phantom: PhantomData }),
|
||||
(_, true) => self.reducing_checked_add(other)
|
||||
}
|
||||
} else {
|
||||
let common_denom: i64 = self.denominator * other.denominator;
|
||||
|
@ -102,7 +193,7 @@ impl Fraction {
|
|||
(common_denom / other.denominator).checked_mul(other.numerator)
|
||||
.and_then(|other_numer| {
|
||||
self_numer.checked_add(other_numer)
|
||||
.map(|numerator| Fraction { numerator, denominator: common_denom })
|
||||
.map(|numerator| Frac { numerator, denominator: common_denom, phantom: PhantomData })
|
||||
})
|
||||
})
|
||||
// Something overflowed - try reducing the inputs first.
|
||||
|
@ -116,33 +207,29 @@ impl Fraction {
|
|||
pub fn add(&mut self, other: &mut Self) -> Self {
|
||||
if self.denominator == other.denominator {
|
||||
// Happy path - we get to skip calculating a common denominator!
|
||||
match self.numerator.checked_add(other.numerator) {
|
||||
// There's a case to be made that we shouldn't do a checked_add here.
|
||||
// For this to avoid an overflow, all of the following must be true:
|
||||
//
|
||||
// 1. The two denominators are the same. (We already verified this.)
|
||||
// 2. When added, the two numerators overflow.
|
||||
// 3. When both fractions are reduced, and we find a common denominator, this new common denominator
|
||||
// is smaller than the current (already common) denominator the two fractions share.
|
||||
// 4. When adding the revised numerators, with the new common denominator, they no longer overflow.
|
||||
//
|
||||
// What are the odds that all of these will be true?
|
||||
Some(numerator) => Fraction { numerator, denominator: self.denominator },
|
||||
None => self.reducing_add(other)
|
||||
match self.numerator.overflowing_add(other.numerator) {
|
||||
(numerator, false) => Frac { numerator, denominator: self.denominator, phantom: PhantomData },
|
||||
(_, true) => self.reducing_add(other)
|
||||
}
|
||||
} else {
|
||||
let common_denom: i64 = self.denominator * other.denominator;
|
||||
|
||||
(common_denom / self.denominator).checked_mul(self.numerator)
|
||||
.and_then(|self_numer| {
|
||||
(common_denom / other.denominator).checked_mul(other.numerator)
|
||||
.and_then(|other_numer| {
|
||||
self_numer.checked_add(other_numer)
|
||||
.map(|numerator| Fraction { numerator, denominator: common_denom })
|
||||
})
|
||||
})
|
||||
// Something overflowed - try reducing the inputs first.
|
||||
.unwrap_or_else(|| self.reducing_add(other))
|
||||
// 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) => Frac { numerator, denominator: common_denom, phantom: PhantomData },
|
||||
(_, true) => self.reducing_add(other)
|
||||
}
|
||||
},
|
||||
(_, true) => self.reducing_add(other)
|
||||
}
|
||||
},
|
||||
(_, true) => self.reducing_add(other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +245,7 @@ impl Fraction {
|
|||
(self.numerator * (denominator / self.denominator))
|
||||
+ (other.numerator * (denominator / other.denominator));
|
||||
|
||||
Fraction { numerator, denominator }
|
||||
Frac { numerator, denominator, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Add while sacrificing performance to avoid overflow.
|
||||
|
@ -173,7 +260,7 @@ impl Fraction {
|
|||
(denominator / self.denominator).checked_mul(self.numerator).and_then(|self_numerator|
|
||||
(denominator / other.denominator).checked_mul(other.numerator).and_then(|other_numerator|
|
||||
self_numerator.checked_add(other_numerator).map(|numerator|
|
||||
Fraction { numerator, denominator }
|
||||
Frac { numerator, denominator, phantom: PhantomData }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -187,7 +274,7 @@ impl Fraction {
|
|||
if self.denominator == other.denominator {
|
||||
match self.numerator.checked_sub(other.numerator) {
|
||||
// Happy path - we get to skip calculating a common denominator!
|
||||
Some(numerator) => Some(Fraction { numerator, denominator: self.denominator }),
|
||||
Some(numerator) => Some(Frac { numerator, denominator: self.denominator, phantom: PhantomData }),
|
||||
None => self.reducing_checked_sub(other)
|
||||
}
|
||||
} else {
|
||||
|
@ -198,7 +285,7 @@ impl Fraction {
|
|||
(common_denom / other.denominator).checked_mul(other.numerator)
|
||||
.and_then(|other_numer| {
|
||||
self_numer.checked_sub(other_numer)
|
||||
.map(|numerator| Fraction { numerator, denominator: common_denom })
|
||||
.map(|numerator| Frac { numerator, denominator: common_denom, phantom: PhantomData })
|
||||
})
|
||||
})
|
||||
// Something overflowed - try reducing the inputs first.
|
||||
|
@ -211,24 +298,30 @@ impl Fraction {
|
|||
/// that calculation later.
|
||||
pub fn sub(&mut self, other: &mut Self) -> Self {
|
||||
if self.denominator == other.denominator {
|
||||
match self.numerator.checked_sub(other.numerator) {
|
||||
// Happy path - we get to skip calculating a common denominator!
|
||||
Some(numerator) => Fraction { numerator, denominator: self.denominator },
|
||||
None => self.reducing_sub(other)
|
||||
match self.numerator.overflowing_sub(other.numerator) {
|
||||
(numerator, false) => Frac { numerator, denominator: self.denominator, phantom: PhantomData },
|
||||
(_, true) => self.reducing_sub(other)
|
||||
}
|
||||
} else {
|
||||
let common_denom: i64 = self.denominator * other.denominator;
|
||||
|
||||
(common_denom / self.denominator).checked_mul(self.numerator)
|
||||
.and_then(|self_numer| {
|
||||
(common_denom / other.denominator).checked_mul(other.numerator)
|
||||
.and_then(|other_numer| {
|
||||
self_numer.checked_sub(other_numer)
|
||||
.map(|numerator| Fraction { numerator, denominator: common_denom })
|
||||
})
|
||||
})
|
||||
// Something overflowed - try reducing the inputs first.
|
||||
.unwrap_or_else(|| self.reducing_sub(other))
|
||||
// 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) => Frac { numerator, denominator: common_denom, phantom: PhantomData },
|
||||
(_, true) => self.reducing_sub(other)
|
||||
}
|
||||
},
|
||||
(_, true) => self.reducing_sub(other)
|
||||
}
|
||||
},
|
||||
(_, true) => self.reducing_sub(other)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,7 +337,7 @@ impl Fraction {
|
|||
(self.numerator * (denominator / self.denominator))
|
||||
- (other.numerator * (denominator / other.denominator));
|
||||
|
||||
Fraction { numerator, denominator }
|
||||
Frac { numerator, denominator, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Subtract while sacrificing performance to avoid overflow.
|
||||
|
@ -259,7 +352,7 @@ impl Fraction {
|
|||
(denominator / self.denominator).checked_mul(self.numerator).and_then(|self_numerator|
|
||||
(denominator / other.denominator).checked_mul(other.numerator).and_then(|other_numerator|
|
||||
self_numerator.checked_sub(other_numerator).map(|numerator|
|
||||
Fraction { numerator, denominator }
|
||||
Frac { numerator, denominator, phantom: PhantomData }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -285,13 +378,14 @@ impl Fraction {
|
|||
// 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 {
|
||||
Some(Frac {
|
||||
numerator: numerator / self.denominator,
|
||||
denominator: self.denominator
|
||||
denominator: self.denominator,
|
||||
phantom: PhantomData
|
||||
})
|
||||
} else {
|
||||
match self.denominator.checked_mul(other.denominator) {
|
||||
Some(denominator) => Some(Fraction { numerator, denominator }),
|
||||
Some(denominator) => Some(Frac { numerator, denominator, phantom: PhantomData }),
|
||||
|
||||
// Denominator overflowed. See if reducing the inputs helps!
|
||||
None => self.reducing_checked_mul(other)
|
||||
|
@ -308,37 +402,36 @@ impl Fraction {
|
|||
/// the fractions mid-operation, and that reduction should persist to avoid having to redo
|
||||
/// that calculation later.
|
||||
pub fn mul(&mut self, other: &mut Self) -> Self {
|
||||
match self.numerator.checked_mul(other.numerator) {
|
||||
Some(numerator) => {
|
||||
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.checked_rem(self.denominator)
|
||||
.map(|rem| rem == 0)
|
||||
.unwrap_or(false)
|
||||
&& (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 {
|
||||
Frac {
|
||||
numerator: numerator / self.denominator,
|
||||
denominator: self.denominator
|
||||
denominator: self.denominator,
|
||||
phantom: PhantomData
|
||||
}
|
||||
} else {
|
||||
match self.denominator.checked_mul(other.denominator) {
|
||||
Some(denominator) => Fraction { numerator, denominator },
|
||||
match self.denominator.overflowing_mul(other.denominator) {
|
||||
(denominator, false) => Frac { numerator, denominator, phantom: PhantomData },
|
||||
|
||||
// Denominator overflowed. See if reducing the inputs helps!
|
||||
None => self.reducing_mul(other)
|
||||
(_, true) => self.reducing_mul(other)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Numerator overflowed. See if reducing the inputs helps!
|
||||
None => self.reducing_mul(other)
|
||||
(_, true) => self.reducing_mul(other)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,7 +448,7 @@ impl Fraction {
|
|||
match self.numerator.checked_mul(other.denominator) {
|
||||
Some(numerator) => {
|
||||
match self.denominator.checked_mul(other.numerator) {
|
||||
Some(denominator) => Some(Fraction { numerator, denominator }),
|
||||
Some(denominator) => Some(Frac { numerator, denominator, phantom: PhantomData }),
|
||||
// Denominator overflowed. See if reducing the inputs helps!
|
||||
None => self.reducing_checked_div(other)
|
||||
}
|
||||
|
@ -366,21 +459,48 @@ impl Fraction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Divide two fractions, panicking if given a 0 denominator.
|
||||
/// If the Frac is valid, returns it wrapped in Some.
|
||||
/// Otherwise, returns None.
|
||||
pub fn into_valid<V>(self) -> Option<Frac<Valid>> {
|
||||
if self.denominator.is_positive() {
|
||||
Some(unsafe { std::mem::transmute::<Frac<T>, Frac<Valid>>(self) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// If the Frac is valid, returns it with the type variable set accordingly.
|
||||
/// Otherwise, returns the fallback value.
|
||||
pub fn valid_or<V>(self, fallback: V) -> Result<Frac<Valid>, V> {
|
||||
if self.denominator.is_positive() {
|
||||
Ok(unsafe { std::mem::transmute::<Frac<T>, Frac<Valid>>(self) })
|
||||
} else {
|
||||
Err(fallback)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn valid_or_else<F, V>(self, fallback_fn: F) -> Result<Frac<Valid>, V>
|
||||
where F: Fn() -> V
|
||||
{
|
||||
if self.denominator.is_positive() {
|
||||
Ok(unsafe { std::mem::transmute::<Frac<T>, Frac<Valid>>(self) })
|
||||
} else {
|
||||
Err(fallback_fn())
|
||||
}
|
||||
}
|
||||
|
||||
/// Divide two fractions.
|
||||
///
|
||||
/// This returns a Frac with an unbound type parameter because the result may not be valid.
|
||||
///
|
||||
/// This requires mutable references because it may decide to reduce
|
||||
/// the fractions mid-operation, and that reduction should persist to avoid having to redo
|
||||
/// that calculation later.
|
||||
pub fn div_or_panic(&mut self, other: &mut Self) -> Self {
|
||||
// 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 {
|
||||
panic!("Division by zero.");
|
||||
}
|
||||
|
||||
pub fn div<U, V>(&mut self, other: &mut Frac<U>) -> Frac<V> {
|
||||
match self.numerator.checked_mul(other.denominator) {
|
||||
Some(numerator) => {
|
||||
match self.denominator.checked_mul(other.numerator) {
|
||||
Some(denominator) => Fraction { numerator, denominator },
|
||||
Some(denominator) => Frac { numerator, denominator, phantom: PhantomData },
|
||||
// Denominator overflowed. See if reducing the inputs helps!
|
||||
None => self.reducing_div(other)
|
||||
}
|
||||
|
@ -402,7 +522,7 @@ impl Fraction {
|
|||
let numerator = self.numerator * other.numerator;
|
||||
let denominator = self.denominator * other.denominator;
|
||||
|
||||
Fraction { numerator, denominator }
|
||||
Frac { numerator, denominator, phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Multiply while sacrificing performance to avoid overflow.
|
||||
|
@ -416,7 +536,7 @@ impl Fraction {
|
|||
self.numerator.checked_mul(other.numerator)
|
||||
.and_then(|numerator|
|
||||
self.denominator.checked_mul(other.denominator)
|
||||
.map(|denominator| Fraction { numerator, denominator })
|
||||
.map(|denominator| Frac { numerator, denominator, phantom: PhantomData })
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -424,39 +544,33 @@ impl Fraction {
|
|||
/// 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(&mut self, other: &mut Self) -> Option<Self> {
|
||||
// This should have been verified already by this point!
|
||||
assert_ne!(other.numerator, 0);
|
||||
|
||||
self.reduce();
|
||||
other.reduce();
|
||||
|
||||
self.numerator.checked_mul(other.denominator)
|
||||
.and_then(|numerator|
|
||||
self.denominator.checked_mul(other.numerator)
|
||||
.map(|denominator| Fraction { numerator, denominator })
|
||||
.map(|denominator| Frac { numerator, denominator, phantom: PhantomData })
|
||||
)
|
||||
}
|
||||
|
||||
/// 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(&mut self, other: &mut Self) -> Self {
|
||||
// This should have been verified already by this point!
|
||||
assert_ne!(other.numerator, 0);
|
||||
|
||||
fn reducing_div<U, V>(&mut self, other: &mut Frac<U>) -> Frac<V> {
|
||||
self.reduce();
|
||||
other.reduce();
|
||||
|
||||
let numerator = self.numerator * other.denominator;
|
||||
let denominator = self.denominator * other.numerator;
|
||||
|
||||
Fraction { numerator, denominator }
|
||||
Frac { numerator, denominator, phantom: PhantomData }
|
||||
}
|
||||
|
||||
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
|
||||
fn reduced_eq(&self, other: &Self) -> bool {
|
||||
let mut reduced_self = self.clone();
|
||||
let mut reduced_other = other.clone();
|
||||
let mut reduced_self: Frac<Valid> = Frac { numerator: self.numerator, denominator: self.denominator, phantom: PhantomData };
|
||||
let mut reduced_other: Frac<Valid> = Frac { numerator: other.numerator, denominator: other.denominator, phantom: PhantomData };
|
||||
|
||||
reduced_self.reduce();
|
||||
reduced_other.reduce();
|
||||
|
@ -470,8 +584,8 @@ impl Fraction {
|
|||
|
||||
#[inline(never)] // We don't want to inline this because it should be almost never invoked in practice.
|
||||
fn reduced_cmp(&self, other: &Self) -> Ordering {
|
||||
let mut reduced_self = self.clone();
|
||||
let mut reduced_other = other.clone();
|
||||
let mut reduced_self: Frac<Valid> = Frac { numerator: self.numerator, denominator: self.denominator, phantom: PhantomData };
|
||||
let mut reduced_other: Frac<Valid> = Frac { numerator: other.numerator, denominator: other.denominator, phantom: PhantomData };
|
||||
|
||||
reduced_self.reduce();
|
||||
reduced_other.reduce();
|
||||
|
@ -484,13 +598,13 @@ impl Fraction {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Fraction {
|
||||
impl<T> fmt::Debug for Frac<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.numerator, self.denominator)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Fraction {
|
||||
impl<T> PartialEq for Frac<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.denominator == other.denominator {
|
||||
self.numerator == other.numerator
|
||||
|
@ -510,14 +624,14 @@ impl PartialEq for Fraction {
|
|||
}
|
||||
}
|
||||
|
||||
impl Eq for Fraction {}
|
||||
impl PartialOrd for Fraction {
|
||||
impl<T> Eq for Frac<T> {}
|
||||
impl<T> PartialOrd for Frac<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Fraction {
|
||||
impl<T> Ord for Frac<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.denominator == other.denominator {
|
||||
self.numerator.cmp(&other.numerator)
|
||||
|
@ -537,62 +651,64 @@ impl Ord for Fraction {
|
|||
}
|
||||
}
|
||||
|
||||
impl Hash for Fraction {
|
||||
impl<T> Hash for Frac<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
let (numerator, denominator) = self.clone().reduced();
|
||||
let mut cloned: Frac<Valid> = Frac { numerator: self.numerator, denominator: self.denominator, phantom: PhantomData };
|
||||
|
||||
numerator.hash(state);
|
||||
denominator.hash(state);
|
||||
cloned.reduce();
|
||||
|
||||
cloned.numerator.hash(state);
|
||||
cloned.denominator.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Fraction {
|
||||
type Output = Fraction;
|
||||
impl<T> Neg for Frac<T> {
|
||||
type Output = Frac<T>;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Fraction { numerator: -self.numerator, denominator: self.denominator }
|
||||
Frac { numerator: -self.numerator, denominator: self.denominator, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Fraction {
|
||||
fn from (numerator: u8) -> Fraction {
|
||||
Fraction { numerator: numerator as i64, denominator: 1 }
|
||||
impl<T> From<u8> for Frac<T> {
|
||||
fn from (numerator: u8) -> Frac<T> {
|
||||
Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for Fraction {
|
||||
fn from (numerator: i8) -> Fraction {
|
||||
Fraction { numerator: numerator as i64, denominator: 1 }
|
||||
impl<T> From<i8> for Frac<T> {
|
||||
fn from (numerator: i8) -> Frac<T> {
|
||||
Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Fraction {
|
||||
fn from (numerator: u16) -> Fraction {
|
||||
Fraction { numerator: numerator as i64, denominator: 1 }
|
||||
impl<T> From<u16> for Frac<T> {
|
||||
fn from (numerator: u16) -> Frac<T> {
|
||||
Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for Fraction {
|
||||
fn from (numerator: i16) -> Fraction {
|
||||
Fraction { numerator: numerator as i64, denominator: 1 }
|
||||
impl<T> From<i16> for Frac<T> {
|
||||
fn from (numerator: i16) -> Frac<T> {
|
||||
Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Fraction {
|
||||
fn from (numerator: u32) -> Fraction {
|
||||
Fraction { numerator: numerator as i64, denominator: 1 }
|
||||
impl<T> From<u32> for Frac<T> {
|
||||
fn from (numerator: u32) -> Frac<T> {
|
||||
Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Fraction {
|
||||
fn from (numerator: i32) -> Fraction {
|
||||
Fraction { numerator: numerator as i64, denominator: 1 }
|
||||
impl<T> From<i32> for Frac<T> {
|
||||
fn from (numerator: i32) -> Frac<T> {
|
||||
Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Fraction {
|
||||
fn from (numerator: i64) -> Fraction {
|
||||
Fraction { numerator, denominator: 1 }
|
||||
impl<T> From<i64> for Frac<T> {
|
||||
fn from (numerator: i64) -> Frac<T> {
|
||||
Frac { numerator, denominator: 1, phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -651,10 +767,10 @@ fn lcm(me: i64, other: i64) -> i64 {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test_fast_fraction {
|
||||
use super::Fraction;
|
||||
use super::{Frac, Valid};
|
||||
|
||||
fn frac(numerator: i64, denominator: i64) -> Fraction {
|
||||
Fraction::new_from_positive_denominator(numerator, denominator)
|
||||
pub fn frac(numerator: i64, denominator: i64) -> Frac<Valid> {
|
||||
super::new(numerator, denominator)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -684,7 +800,7 @@ mod test_fast_fraction {
|
|||
#[test]
|
||||
fn divide() {
|
||||
assert_eq!(
|
||||
frac(2, 3).div_or_panic(&mut frac(5, 7)),
|
||||
frac(2, 3).div(&mut frac(5, 7)),
|
||||
frac(14, 15)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ pub mod eval;
|
|||
pub mod operator;
|
||||
pub mod region;
|
||||
pub mod fast_fraction;
|
||||
pub mod stdlib;
|
||||
// mod ena;
|
||||
|
||||
// #[macro_use]
|
||||
|
|
58
src/stdlib.rs
Normal file
58
src/stdlib.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Approx is stored as an f64 under the hood.
|
||||
///
|
||||
/// However, part of Roc's design is that Roc users never encounter Infinity,
|
||||
/// -Infinity, NaN, or -0. To Roc application authors, the only difference between
|
||||
/// Approx and Frac is that Approx supports a few more operations (sqrt,
|
||||
/// trigonometry, etc) and is potentially imprecise.
|
||||
///
|
||||
/// To achieve this, Roc maps all invalid Float values (NaN, Infinity, -Infinity) to
|
||||
/// Err values. This means that any value which could contain one of these bit patterns
|
||||
/// is typed as Result { ok: Approx, err: InvalidApprox }, including any Approx values
|
||||
/// passed into Roc.
|
||||
///
|
||||
/// Roc code does not have the expressive power to create NaN, Infinity, or -Infinity,
|
||||
/// so the Approx type inside Roc represents an f64 that is guaratneed not to be NaN,
|
||||
/// Infinity, or -Infinity.
|
||||
///
|
||||
/// Additionally, the implementation detail of 0 and -0 being different f64 values does
|
||||
/// not reach Roc code because there is no way to convert an Approx directly to a String.
|
||||
/// Instead, Approx must go through conversion to either a Frac or an Int, neither of
|
||||
/// which is capable of representing -0. In f64 operations, 0 and -0 are considered
|
||||
/// equivalent, so the distinction does not matter there either.
|
||||
pub struct Approx<T> {
|
||||
value: f64,
|
||||
phantom: PhantomData<T>
|
||||
}
|
||||
|
||||
/// A plain old i64.
|
||||
pub struct Int(i64);
|
||||
|
||||
/// A plain old bool.
|
||||
pub type Bool = bool;
|
||||
|
||||
fn underflow_panic() -> ! {
|
||||
panic!("Underflow!");
|
||||
}
|
||||
|
||||
impl Int {
|
||||
pub fn abs(&self) -> Self {
|
||||
let Int(int_self) = self;
|
||||
|
||||
let (output, underflowed) = int_self.overflowing_abs();
|
||||
|
||||
if underflowed {
|
||||
underflow_panic();
|
||||
}
|
||||
|
||||
Int(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Approx<T> {
|
||||
pub fn abs(num: &Self) -> Self {
|
||||
Approx { value: num.value.abs(), phantom: PhantomData }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue