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::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)
);
}

View file

@ -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
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 }
}
}