Add some stdlib stuff

This commit is contained in:
Richard Feldman 2019-07-11 22:48:26 -04:00
parent f4788b9079
commit 5da8a29015
3 changed files with 678 additions and 503 deletions

View file

@ -5,38 +5,78 @@ use std::ops::Neg;
use std::mem; use std::mem;
use std::fmt; use std::fmt;
use std::marker::PhantomData;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Fraction { pub struct Frac<T> {
numerator: i64, 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 { pub struct Valid;
/// Returns a new Fraction unless the given denominator was zero,
/// in which case returns None. /// Returns a new Fraction where the denominator is guaranteed to be non-negative.
/// ///
/// For a more efficient (but astronomically less safe) alternative, /// If the provided denominator is negative, both numerator and denominator will
/// see new_prereduced_with_positive_denominator. /// be negated; this will result in a mathematically equivalent fraction, but with the
pub fn new(numerator: i64, denominator: i64) -> Option<Fraction> { /// 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() { if denominator.is_positive() {
Some(Fraction { numerator, denominator }) Frac { numerator, denominator, phantom: PhantomData }
} else if denominator == 0 {
None
} else { } else {
// Denominator may never be negative. This lets us avoid -0, // Denominator may never be negative. This lets us avoid -0,
// get the sign merely by returning the numerator's sign, etc. // 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 impl<T> Frac<T> {
/// the denominator is not positive.
#[inline]
pub fn new_from_positive_denominator(numerator: i64, denominator: i64) -> Fraction {
assert!(denominator > 0);
Fraction { numerator, denominator }
}
#[inline] #[inline]
/// Reduces the fraction in place. /// Reduces the fraction in place.
@ -61,6 +101,10 @@ impl Fraction {
self.denominator self.denominator
} }
pub fn is_rational(&self) -> bool {
self.denominator.is_positive()
}
#[inline] #[inline]
/// Reduces the fraction, then returns a tuple of (numerator, denominator). /// Reduces the fraction, then returns a tuple of (numerator, denominator).
pub fn reduced(&mut self) -> ( i64, i64 ) { pub fn reduced(&mut self) -> ( i64, i64 ) {
@ -83,6 +127,53 @@ impl Fraction {
self.numerator == 0 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! /// 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 /// This requires mutable references because it may decide to reduce
/// the fractions mid-operation, and that reduction should persist to avoid having to redo /// 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> { pub fn checked_add(&mut self, other: &mut Self) -> Option<Self> {
if self.denominator == other.denominator { if self.denominator == other.denominator {
// Happy path - we get to skip calculating a common denominator! // Happy path - we get to skip calculating a common denominator!
match self.numerator.checked_add(other.numerator) { match self.numerator.overflowing_add(other.numerator) {
Some(numerator) => Some(Fraction { numerator, denominator: self.denominator }), (numerator, false) => Some(Frac { numerator, denominator: self.denominator, phantom: PhantomData }),
None => self.reducing_checked_add(other) (_, true) => self.reducing_checked_add(other)
} }
} else { } else {
let common_denom: i64 = self.denominator * other.denominator; let common_denom: i64 = self.denominator * other.denominator;
@ -102,7 +193,7 @@ impl Fraction {
(common_denom / other.denominator).checked_mul(other.numerator) (common_denom / other.denominator).checked_mul(other.numerator)
.and_then(|other_numer| { .and_then(|other_numer| {
self_numer.checked_add(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. // Something overflowed - try reducing the inputs first.
@ -116,33 +207,29 @@ impl Fraction {
pub fn add(&mut self, other: &mut Self) -> Self { pub fn add(&mut self, other: &mut Self) -> Self {
if self.denominator == other.denominator { if self.denominator == other.denominator {
// Happy path - we get to skip calculating a common denominator! // Happy path - we get to skip calculating a common denominator!
match self.numerator.checked_add(other.numerator) { match self.numerator.overflowing_add(other.numerator) {
// There's a case to be made that we shouldn't do a checked_add here. (numerator, false) => Frac { numerator, denominator: self.denominator, phantom: PhantomData },
// For this to avoid an overflow, all of the following must be true: (_, true) => self.reducing_add(other)
//
// 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)
} }
} else { } else {
let common_denom: i64 = self.denominator * other.denominator; let common_denom: i64 = self.denominator * other.denominator;
(common_denom / self.denominator).checked_mul(self.numerator) // This code would look nicer with checked_ instead of overflowing_, but
.and_then(|self_numer| { // seems likely the perf would be worse.
(common_denom / other.denominator).checked_mul(other.numerator) match (common_denom / self.denominator).overflowing_mul(self.numerator) {
.and_then(|other_numer| { (self_numer, false) => {
self_numer.checked_add(other_numer) match (common_denom / other.denominator).overflowing_mul(other.numerator) {
.map(|numerator| Fraction { numerator, denominator: common_denom }) (other_numer, false) => {
}) match self_numer.overflowing_add(other_numer) {
}) (numerator, false) => Frac { numerator, denominator: common_denom, phantom: PhantomData },
// Something overflowed - try reducing the inputs first. (_, true) => self.reducing_add(other)
.unwrap_or_else(|| 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)) (self.numerator * (denominator / self.denominator))
+ (other.numerator * (denominator / other.denominator)); + (other.numerator * (denominator / other.denominator));
Fraction { numerator, denominator } Frac { numerator, denominator, phantom: PhantomData }
} }
/// Add while sacrificing performance to avoid overflow. /// 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 / self.denominator).checked_mul(self.numerator).and_then(|self_numerator|
(denominator / other.denominator).checked_mul(other.numerator).and_then(|other_numerator| (denominator / other.denominator).checked_mul(other.numerator).and_then(|other_numerator|
self_numerator.checked_add(other_numerator).map(|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 { if self.denominator == other.denominator {
match self.numerator.checked_sub(other.numerator) { match self.numerator.checked_sub(other.numerator) {
// Happy path - we get to skip calculating a common denominator! // 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) None => self.reducing_checked_sub(other)
} }
} else { } else {
@ -198,7 +285,7 @@ impl Fraction {
(common_denom / other.denominator).checked_mul(other.numerator) (common_denom / other.denominator).checked_mul(other.numerator)
.and_then(|other_numer| { .and_then(|other_numer| {
self_numer.checked_sub(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. // Something overflowed - try reducing the inputs first.
@ -211,24 +298,30 @@ impl Fraction {
/// that calculation later. /// that calculation later.
pub fn sub(&mut self, other: &mut Self) -> Self { pub fn sub(&mut self, other: &mut Self) -> Self {
if self.denominator == other.denominator { if self.denominator == other.denominator {
match self.numerator.checked_sub(other.numerator) {
// Happy path - we get to skip calculating a common denominator! // Happy path - we get to skip calculating a common denominator!
Some(numerator) => Fraction { numerator, denominator: self.denominator }, match self.numerator.overflowing_sub(other.numerator) {
None => self.reducing_sub(other) (numerator, false) => Frac { numerator, denominator: self.denominator, phantom: PhantomData },
(_, true) => self.reducing_sub(other)
} }
} else { } else {
let common_denom: i64 = self.denominator * other.denominator; let common_denom: i64 = self.denominator * other.denominator;
(common_denom / self.denominator).checked_mul(self.numerator) // This code would look nicer with checked_ instead of overflowing_, but
.and_then(|self_numer| { // seems likely the perf would be worse.
(common_denom / other.denominator).checked_mul(other.numerator) match (common_denom / self.denominator).overflowing_mul(self.numerator) {
.and_then(|other_numer| { (self_numer, false) => {
self_numer.checked_sub(other_numer) match (common_denom / other.denominator).overflowing_mul(other.numerator) {
.map(|numerator| Fraction { numerator, denominator: common_denom }) (other_numer, false) => {
}) match self_numer.overflowing_sub(other_numer) {
}) (numerator, false) => Frac { numerator, denominator: common_denom, phantom: PhantomData },
// Something overflowed - try reducing the inputs first. (_, true) => self.reducing_sub(other)
.unwrap_or_else(|| 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)) (self.numerator * (denominator / self.denominator))
- (other.numerator * (denominator / other.denominator)); - (other.numerator * (denominator / other.denominator));
Fraction { numerator, denominator } Frac { numerator, denominator, phantom: PhantomData }
} }
/// Subtract while sacrificing performance to avoid overflow. /// 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 / self.denominator).checked_mul(self.numerator).and_then(|self_numerator|
(denominator / other.denominator).checked_mul(other.numerator).and_then(|other_numerator| (denominator / other.denominator).checked_mul(other.numerator).and_then(|other_numerator|
self_numerator.checked_sub(other_numerator).map(|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 // instruction, grab the remainder value out of the register, then
// do the test, and if it passes, grab the existing result of the division // 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. // out of the other register without issuing a second division instruction.
Some(Fraction { Some(Frac {
numerator: numerator / self.denominator, numerator: numerator / self.denominator,
denominator: self.denominator denominator: self.denominator,
phantom: PhantomData
}) })
} else { } else {
match self.denominator.checked_mul(other.denominator) { 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! // Denominator overflowed. See if reducing the inputs helps!
None => self.reducing_checked_mul(other) 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 /// the fractions mid-operation, and that reduction should persist to avoid having to redo
/// that calculation later. /// that calculation later.
pub fn mul(&mut self, other: &mut Self) -> Self { pub fn mul(&mut self, other: &mut Self) -> Self {
match self.numerator.checked_mul(other.numerator) { match self.numerator.overflowing_mul(other.numerator) {
Some(numerator) => { (numerator, false) => {
// Common denominator is valuable. If we have it, try to preserve it! // Common denominator is valuable. If we have it, try to preserve it!
if self.denominator == other.denominator if self.denominator == other.denominator
// See if the denominator is evenly divisible by the new numerator. // See if the denominator is evenly divisible by the new numerator.
// If it is, we can "pre-reduce" to the original denominator! // If it is, we can "pre-reduce" to the original denominator!
&& numerator.checked_rem(self.denominator) && (numerator.overflowing_rem(self.denominator) == (0, false))
.map(|rem| rem == 0)
.unwrap_or(false)
{ {
// TODO There's probably an optimization opportunity here. Check the // TODO There's probably an optimization opportunity here. Check the
// generated instructions - there might be a way to use a division-with-remainder // generated instructions - there might be a way to use a division-with-remainder
// instruction, grab the remainder value out of the register, then // instruction, grab the remainder value out of the register, then
// do the test, and if it passes, grab the existing result of the division // 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. // out of the other register without issuing a second division instruction.
Fraction { Frac {
numerator: numerator / self.denominator, numerator: numerator / self.denominator,
denominator: self.denominator denominator: self.denominator,
phantom: PhantomData
} }
} else { } else {
match self.denominator.checked_mul(other.denominator) { match self.denominator.overflowing_mul(other.denominator) {
Some(denominator) => Fraction { numerator, denominator }, (denominator, false) => Frac { numerator, denominator, phantom: PhantomData },
// Denominator overflowed. See if reducing the inputs helps! // 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! // 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) { match self.numerator.checked_mul(other.denominator) {
Some(numerator) => { Some(numerator) => {
match self.denominator.checked_mul(other.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! // Denominator overflowed. See if reducing the inputs helps!
None => self.reducing_checked_div(other) 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 /// This requires mutable references because it may decide to reduce
/// the fractions mid-operation, and that reduction should persist to avoid having to redo /// the fractions mid-operation, and that reduction should persist to avoid having to redo
/// that calculation later. /// that calculation later.
pub fn div_or_panic(&mut self, other: &mut Self) -> Self { pub fn div<U, V>(&mut self, other: &mut Frac<U>) -> Frac<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 {
panic!("Division by zero.");
}
match self.numerator.checked_mul(other.denominator) { match self.numerator.checked_mul(other.denominator) {
Some(numerator) => { Some(numerator) => {
match self.denominator.checked_mul(other.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! // Denominator overflowed. See if reducing the inputs helps!
None => self.reducing_div(other) None => self.reducing_div(other)
} }
@ -402,7 +522,7 @@ impl Fraction {
let numerator = self.numerator * other.numerator; let numerator = self.numerator * other.numerator;
let denominator = self.denominator * other.denominator; let denominator = self.denominator * other.denominator;
Fraction { numerator, denominator } Frac { numerator, denominator, phantom: PhantomData }
} }
/// Multiply while sacrificing performance to avoid overflow. /// Multiply while sacrificing performance to avoid overflow.
@ -416,7 +536,7 @@ impl Fraction {
self.numerator.checked_mul(other.numerator) self.numerator.checked_mul(other.numerator)
.and_then(|numerator| .and_then(|numerator|
self.denominator.checked_mul(other.denominator) 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. /// 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. #[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> { 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(); self.reduce();
other.reduce(); other.reduce();
self.numerator.checked_mul(other.denominator) self.numerator.checked_mul(other.denominator)
.and_then(|numerator| .and_then(|numerator|
self.denominator.checked_mul(other.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. /// 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. /// 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. #[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 { fn reducing_div<U, V>(&mut self, other: &mut Frac<U>) -> Frac<V> {
// This should have been verified already by this point!
assert_ne!(other.numerator, 0);
self.reduce(); self.reduce();
other.reduce(); other.reduce();
let numerator = self.numerator * other.denominator; let numerator = self.numerator * other.denominator;
let denominator = self.denominator * other.numerator; 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. #[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 { fn reduced_eq(&self, other: &Self) -> bool {
let mut reduced_self = self.clone(); let mut reduced_self: Frac<Valid> = Frac { numerator: self.numerator, denominator: self.denominator, phantom: PhantomData };
let mut reduced_other = other.clone(); let mut reduced_other: Frac<Valid> = Frac { numerator: other.numerator, denominator: other.denominator, phantom: PhantomData };
reduced_self.reduce(); reduced_self.reduce();
reduced_other.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. #[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 { fn reduced_cmp(&self, other: &Self) -> Ordering {
let mut reduced_self = self.clone(); let mut reduced_self: Frac<Valid> = Frac { numerator: self.numerator, denominator: self.denominator, phantom: PhantomData };
let mut reduced_other = other.clone(); let mut reduced_other: Frac<Valid> = Frac { numerator: other.numerator, denominator: other.denominator, phantom: PhantomData };
reduced_self.reduce(); reduced_self.reduce();
reduced_other.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 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.numerator, self.denominator) write!(f, "{}/{}", self.numerator, self.denominator)
} }
} }
impl PartialEq for Fraction { impl<T> PartialEq for Frac<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
if self.denominator == other.denominator { if self.denominator == other.denominator {
self.numerator == other.numerator self.numerator == other.numerator
@ -510,14 +624,14 @@ impl PartialEq for Fraction {
} }
} }
impl Eq for Fraction {} impl<T> Eq for Frac<T> {}
impl PartialOrd for Fraction { impl<T> PartialOrd for Frac<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for Fraction { impl<T> Ord for Frac<T> {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
if self.denominator == other.denominator { if self.denominator == other.denominator {
self.numerator.cmp(&other.numerator) 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) { 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); cloned.reduce();
denominator.hash(state);
cloned.numerator.hash(state);
cloned.denominator.hash(state);
} }
} }
impl Neg for Fraction { impl<T> Neg for Frac<T> {
type Output = Fraction; type Output = Frac<T>;
fn neg(self) -> Self { 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 { impl<T> From<u8> for Frac<T> {
fn from (numerator: u8) -> Fraction { fn from (numerator: u8) -> Frac<T> {
Fraction { numerator: numerator as i64, denominator: 1 } Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
} }
} }
impl From<i8> for Fraction { impl<T> From<i8> for Frac<T> {
fn from (numerator: i8) -> Fraction { fn from (numerator: i8) -> Frac<T> {
Fraction { numerator: numerator as i64, denominator: 1 } Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
} }
} }
impl From<u16> for Fraction { impl<T> From<u16> for Frac<T> {
fn from (numerator: u16) -> Fraction { fn from (numerator: u16) -> Frac<T> {
Fraction { numerator: numerator as i64, denominator: 1 } Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
} }
} }
impl From<i16> for Fraction { impl<T> From<i16> for Frac<T> {
fn from (numerator: i16) -> Fraction { fn from (numerator: i16) -> Frac<T> {
Fraction { numerator: numerator as i64, denominator: 1 } Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
} }
} }
impl From<u32> for Fraction { impl<T> From<u32> for Frac<T> {
fn from (numerator: u32) -> Fraction { fn from (numerator: u32) -> Frac<T> {
Fraction { numerator: numerator as i64, denominator: 1 } Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
} }
} }
impl From<i32> for Fraction { impl<T> From<i32> for Frac<T> {
fn from (numerator: i32) -> Fraction { fn from (numerator: i32) -> Frac<T> {
Fraction { numerator: numerator as i64, denominator: 1 } Frac { numerator: numerator as i64, denominator: 1, phantom: PhantomData }
} }
} }
impl From<i64> for Fraction { impl<T> From<i64> for Frac<T> {
fn from (numerator: i64) -> Fraction { fn from (numerator: i64) -> Frac<T> {
Fraction { numerator, denominator: 1 } Frac { numerator, denominator: 1, phantom: PhantomData }
} }
} }
@ -651,10 +767,10 @@ fn lcm(me: i64, other: i64) -> i64 {
#[cfg(test)] #[cfg(test)]
mod test_fast_fraction { mod test_fast_fraction {
use super::Fraction; use super::{Frac, Valid};
fn frac(numerator: i64, denominator: i64) -> Fraction { pub fn frac(numerator: i64, denominator: i64) -> Frac<Valid> {
Fraction::new_from_positive_denominator(numerator, denominator) super::new(numerator, denominator)
} }
#[test] #[test]
@ -684,7 +800,7 @@ mod test_fast_fraction {
#[test] #[test]
fn divide() { fn divide() {
assert_eq!( assert_eq!(
frac(2, 3).div_or_panic(&mut frac(5, 7)), frac(2, 3).div(&mut frac(5, 7)),
frac(14, 15) frac(14, 15)
); );
} }

View file

@ -5,6 +5,7 @@ pub mod eval;
pub mod operator; pub mod operator;
pub mod region; pub mod region;
pub mod fast_fraction; pub mod fast_fraction;
pub mod stdlib;
// mod ena; // mod ena;
// #[macro_use] // #[macro_use]

58
src/stdlib.rs Normal file
View 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 }
}
}