Clean up project structure
All checks were successful
per-push tests / build (push) Successful in 32s
per-push tests / test-utility (push) Successful in 32s
per-push tests / test-frontend (push) Successful in 34s
per-push tests / test-backend (push) Successful in 30s
per-push tests / timed-decomposer-parse (push) Successful in 26s
All checks were successful
per-push tests / build (push) Successful in 32s
per-push tests / test-utility (push) Successful in 32s
per-push tests / test-frontend (push) Successful in 34s
per-push tests / test-backend (push) Successful in 30s
per-push tests / timed-decomposer-parse (push) Successful in 26s
The number package is moved into its own package henceforth referred to as "organelle". Hyphae and Mycelium are updated accordingly. In addition, Hyphae gets a copy of the sexpr module of Mycelium. This will not remain a copy, rather it will be the basis of a heap manager module within Mycelium to be worked on in the future. Fixes #32 Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
parent
6f95381e5e
commit
8d2d0ebf0c
12 changed files with 247 additions and 10 deletions
8
organelle/Cargo.toml
Normal file
8
organelle/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "organelle"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
num = { version = "0.4.3", features = ["alloc"] }
|
||||
|
||||
799
organelle/src/lib.rs
Normal file
799
organelle/src/lib.rs
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
/* Mycelium Scheme
|
||||
* Copyright (C) 2025 Ava Affine
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::format;
|
||||
use alloc::fmt::Debug;
|
||||
use core::{cmp::Ordering, f64, ops::{Add, Div, Mul, Sub}, str::FromStr};
|
||||
use num::{integer::{gcd}, pow::Pow};
|
||||
|
||||
pub const E_INCOMPREHENSIBLE: &str = "could not comprehend number literal";
|
||||
pub const E_BASE_PARSE_FAIL: &str = "failed to parse explicit base literal";
|
||||
pub const E_POUND_TRUNCATED: &str = "pound sign implies additional input";
|
||||
pub const E_UNKNOWN_CONTROL: &str = "unknown character in number literal";
|
||||
pub const E_EMPTY_INPUT: &str = "empty string cannot be a number";
|
||||
pub const E_UNKNOWN_SYMBOL: &str = "unknown symbolic number repr";
|
||||
pub const E_NO_DENOMINATOR: &str = "fraction is missing a denominator";
|
||||
pub const E_MULTI_DENOMINATOR: &str = "fraction has too many denominators";
|
||||
pub const E_ZERO_DENOMINATOR: &str = "denominator cannot be zero";
|
||||
pub const E_NUMERATOR_PARSE_FAIL: &str = "couldnt parse numerator";
|
||||
pub const E_DENOMINATOR_PARSE_FAIL: &str = "couldnt parse denominator";
|
||||
pub const E_FLOAT_PARSE_FAIL: &str = "couldnt parse float";
|
||||
pub const E_SCIENTIFIC_E: &str = "scientific notation implies an 'e'";
|
||||
pub const E_SCIENTIFIC_MULTI_E: &str = "scientific notation implies only a single 'e'";
|
||||
pub const E_SCIENTIFIC_OPERAND: &str = "couldnt parse 32 bit float operand";
|
||||
pub const E_SCIENTIFIC_POWER: &str = "couldnt parse integer power";
|
||||
|
||||
pub trait Numeric: Copy + Clone + Debug + FromStr + Into<String> {
|
||||
fn is_exact(&self) -> bool;
|
||||
fn make_inexact(&self) -> Float;
|
||||
fn make_exact(&self) -> Fraction;
|
||||
}
|
||||
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct ScientificNotation (pub f32, pub isize);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum SymbolicNumber {
|
||||
Inf,
|
||||
NegInf,
|
||||
NaN,
|
||||
NegNan,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Fraction (pub isize, pub isize);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Float (pub f64);
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Number {
|
||||
Sci(ScientificNotation),
|
||||
Fra(Fraction),
|
||||
Flt(Float),
|
||||
Sym(SymbolicNumber)
|
||||
}
|
||||
|
||||
impl From<SymbolicNumber> for Number {
|
||||
fn from(value: SymbolicNumber) -> Self {
|
||||
Number::Sym(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScientificNotation> for Number {
|
||||
fn from(value: ScientificNotation) -> Self {
|
||||
Number::Sci(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Fraction> for Number {
|
||||
fn from(value: Fraction) -> Self {
|
||||
Number::Fra(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Float> for Number {
|
||||
fn from(value: Float) -> Self {
|
||||
Number::Flt(value)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: both the following impls should be done with a macro
|
||||
impl Into<String> for Number {
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
Number::Sci(x) => x.into(),
|
||||
Number::Fra(x) => x.into(),
|
||||
Number::Flt(x) => x.into(),
|
||||
Number::Sym(x) => x.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for Number {
|
||||
fn is_exact(&self) -> bool {
|
||||
match self {
|
||||
Number::Sci(x) => x.is_exact(),
|
||||
Number::Fra(x) => x.is_exact(),
|
||||
Number::Flt(x) => x.is_exact(),
|
||||
Number::Sym(x) => x.is_exact(),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_exact(&self) -> Fraction {
|
||||
match self {
|
||||
Number::Sci(x) => x.make_exact(),
|
||||
Number::Fra(x) => x.make_exact(),
|
||||
Number::Flt(x) => x.make_exact(),
|
||||
Number::Sym(x) => x.make_exact(),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_inexact(&self) -> Float {
|
||||
match self {
|
||||
Number::Sci(x) => x.make_inexact(),
|
||||
Number::Fra(x) => x.make_inexact(),
|
||||
Number::Flt(x) => x.make_inexact(),
|
||||
Number::Sym(x) => x.make_inexact(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Number {
|
||||
fn from(value: f64) -> Self {
|
||||
match value {
|
||||
f64::INFINITY => Number::Sym(SymbolicNumber::Inf),
|
||||
f64::NEG_INFINITY => Number::Sym(SymbolicNumber::NegInf),
|
||||
_ if value.is_nan() => Number::Sym(SymbolicNumber::NaN),
|
||||
_ if value.fract() == 0.0 => Number::Fra(Fraction(value as isize, 1)),
|
||||
_ => Number::Flt(Float(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Number {
|
||||
type Err = &'static str;
|
||||
|
||||
/* Number forms
|
||||
* - 1.3
|
||||
* - 1e100
|
||||
* - 1.3e100
|
||||
* - +1.3
|
||||
* - -2.3
|
||||
* - #d124
|
||||
* - #o2535 // base 8
|
||||
* - #x8A3D // base 16
|
||||
* - #b1011 // base 2
|
||||
* - 2/4 // inexact
|
||||
* - #e1/5 // exact (fraction is as is)
|
||||
* - #e1e1 // exact 1e1 (= 10)
|
||||
* - #i1/5 // inexact (collapse fraction to decimal)
|
||||
* - +inf.0, -inf.0, +nan.0, -nan.0
|
||||
*/
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
let maybe_sym = value.parse::<SymbolicNumber>();
|
||||
if maybe_sym.is_ok() {
|
||||
return Ok(Number::Sym(maybe_sym.unwrap()))
|
||||
}
|
||||
|
||||
/* Only two things that we need to handle here.
|
||||
* 1. leading with a +/-
|
||||
* 2. leading with a #i or a #e
|
||||
* These are mutually exclusive options.
|
||||
*
|
||||
* Once they have been managed or ruled out we can
|
||||
* just try each number type
|
||||
*/
|
||||
|
||||
let mut force_exact = false;
|
||||
let mut force_inexact = false;
|
||||
let mut base = 0;
|
||||
let mut iter = value.chars();
|
||||
let mut start_idx: usize = 0;
|
||||
|
||||
match iter.next() {
|
||||
Some('+') => start_idx = 1,
|
||||
Some('-') => start_idx = 0,
|
||||
Some('#') => {
|
||||
start_idx = 2;
|
||||
match iter.next() {
|
||||
None => return Err(E_POUND_TRUNCATED),
|
||||
Some('i') => force_inexact = true,
|
||||
Some('e') => force_exact = true,
|
||||
Some('x') => base = 16,
|
||||
Some('d') => base = 10,
|
||||
Some('o') => base = 8,
|
||||
Some('b') => base = 2,
|
||||
_ => return Err(E_UNKNOWN_CONTROL),
|
||||
}
|
||||
},
|
||||
None => return Err(E_EMPTY_INPUT),
|
||||
_ => ()
|
||||
}
|
||||
|
||||
let substr = &value[start_idx..];
|
||||
let res;
|
||||
|
||||
if base > 0 {
|
||||
let num = isize::from_str_radix(substr, base)
|
||||
.or(Err(E_BASE_PARSE_FAIL))?;
|
||||
return Ok(Number::Fra(Fraction(num, 1)));
|
||||
|
||||
} else if let Ok(num) = substr.parse::<ScientificNotation>() {
|
||||
res = Number::Sci(num);
|
||||
|
||||
} else if let Ok(num) = substr.parse::<Fraction>() {
|
||||
res = Number::Fra(num);
|
||||
|
||||
} else if let Ok(num) = substr.parse::<Float>() {
|
||||
res = Number::Flt(num);
|
||||
|
||||
} else {
|
||||
return Err(E_INCOMPREHENSIBLE)
|
||||
}
|
||||
|
||||
if force_exact {
|
||||
return Ok(Number::Fra(res.make_exact()))
|
||||
}
|
||||
|
||||
if force_inexact {
|
||||
return Ok(Number::Flt(res.make_inexact()))
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Number {
|
||||
type Output = Number;
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
if self.is_exact() && rhs.is_exact() {
|
||||
let Fraction(lnum, lden) = self.make_exact();
|
||||
let Fraction(rnum, rden) = rhs.make_exact();
|
||||
let num = (lnum * rden) + (rnum * lden);
|
||||
let den = lden * rden;
|
||||
Number::Fra(Fraction(num, den).simplify())
|
||||
|
||||
} else {
|
||||
let Float(l) = self.make_inexact();
|
||||
let Float(r) = rhs.make_inexact();
|
||||
let res = l + r;
|
||||
res.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Number {
|
||||
type Output = Number;
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
if self.is_exact() && rhs.is_exact() {
|
||||
let Fraction(lnum, lden) = self.make_exact();
|
||||
let Fraction(rnum, rden) = rhs.make_exact();
|
||||
let num = (lnum * rden) - (rnum * lden);
|
||||
let den = lden * rden;
|
||||
Number::Fra(Fraction(num, den).simplify())
|
||||
|
||||
} else {
|
||||
let Float(l) = self.make_inexact();
|
||||
let Float(r) = rhs.make_inexact();
|
||||
let res = l - r;
|
||||
res.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Number {
|
||||
type Output = Number;
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
if self.is_exact() && rhs.is_exact() {
|
||||
let Fraction(lnum, lden) = self.make_exact();
|
||||
let Fraction(rnum, rden) = rhs.make_exact();
|
||||
let num = lnum * rnum;
|
||||
let den = lden * rden;
|
||||
Number::Fra(Fraction(num, den).simplify())
|
||||
|
||||
} else {
|
||||
let Float(l) = self.make_inexact();
|
||||
let Float(r) = rhs.make_inexact();
|
||||
let res = l * r;
|
||||
res.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Number {
|
||||
type Output = Number;
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
if self.is_exact() && rhs.is_exact() {
|
||||
let Fraction(lnum, lden) = self.make_exact();
|
||||
let Fraction(rnum, rden) = rhs.make_exact();
|
||||
let num = lnum * rden;
|
||||
let den = rnum * lden;
|
||||
Number::Fra(Fraction(num, den).simplify())
|
||||
|
||||
} else {
|
||||
let Float(l) = self.make_inexact();
|
||||
let Float(r) = rhs.make_inexact();
|
||||
let res = l / r;
|
||||
res.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pow<Number> for Number {
|
||||
type Output = Number;
|
||||
fn pow(self, rhs: Number) -> Self::Output {
|
||||
if self.is_exact() && rhs.is_exact() {
|
||||
let Fraction(mut lnum, mut lden) = self
|
||||
.make_exact()
|
||||
.simplify();
|
||||
let Fraction(rnum, rden) = rhs
|
||||
.make_exact()
|
||||
.simplify();
|
||||
|
||||
// flip first frac if second one is negative
|
||||
if rnum < 0 {
|
||||
let tmp = lnum;
|
||||
lnum = lden;
|
||||
lden = tmp;
|
||||
}
|
||||
|
||||
// apply fractional exponent (denominator)
|
||||
let mut intermediate_numer = f64::powf(lnum as f64, 1.0 / rden as f64);
|
||||
let mut intermediate_denom = f64::powf(lden as f64, 1.0 / rden as f64);
|
||||
|
||||
// apply whole exponent (numerator)
|
||||
intermediate_numer =
|
||||
f64::powf(intermediate_numer, rnum.abs() as f64);
|
||||
intermediate_denom =
|
||||
f64::powf(intermediate_denom, rnum.abs() as f64);
|
||||
|
||||
if intermediate_numer.fract() == 0.0 && intermediate_denom.fract() == 0.0 {
|
||||
Number::Fra(Fraction(intermediate_numer as isize, intermediate_denom as isize)
|
||||
.simplify())
|
||||
} else {
|
||||
(intermediate_numer / intermediate_denom).into()
|
||||
}
|
||||
|
||||
// one or both are already inexact
|
||||
} else {
|
||||
let Float(l) = self.make_inexact();
|
||||
let Float(r) = rhs.make_inexact();
|
||||
let res = f64::powf(l, r);
|
||||
res.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Number {
|
||||
fn eq(&self, other: &Number) -> bool {
|
||||
//if self.is_exact() && other.is_exact() {
|
||||
// TODO: Figure out a way to comp two fractions without reducing
|
||||
// to a float and losing the precision of exact numbers
|
||||
//} else {
|
||||
self.make_inexact() == other.make_inexact()
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Number{
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
//if self.is_exact() && other.is_exact() {
|
||||
// TODO: Figure out a way to comp two fractions without reducing
|
||||
// to a float and losing the precision of exact numbers
|
||||
//} else {
|
||||
self.make_inexact().0.partial_cmp(&other.make_inexact().0)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SymbolicNumber {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value {
|
||||
"+inf.0" => Ok(SymbolicNumber::Inf),
|
||||
"-inf.0" => Ok(SymbolicNumber::NegInf),
|
||||
"+nan.0" => Ok(SymbolicNumber::NaN),
|
||||
"-nan.0" => Ok(SymbolicNumber::NegNan),
|
||||
_ => Err(E_UNKNOWN_SYMBOL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for SymbolicNumber {
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
SymbolicNumber::Inf => format!("+inf.0"),
|
||||
SymbolicNumber::NegInf => format!("-inf.0"),
|
||||
SymbolicNumber::NaN => format!("+nan.0"),
|
||||
SymbolicNumber::NegNan => format!("-nan.0"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for SymbolicNumber {
|
||||
fn is_exact(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn make_inexact(&self) -> Float {
|
||||
match self {
|
||||
SymbolicNumber::Inf => Float(f64::INFINITY),
|
||||
SymbolicNumber::NegInf => Float(f64::NEG_INFINITY),
|
||||
SymbolicNumber::NaN | SymbolicNumber::NegNan => Float(f64::NAN),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_exact(&self) -> Fraction {
|
||||
panic!("attempted to make inf or nan into fraction")
|
||||
}
|
||||
}
|
||||
|
||||
impl Fraction {
|
||||
fn simplify(&self) -> Fraction {
|
||||
let mut n = self.0;
|
||||
let mut d = self.1;
|
||||
/* normalize negative to numerator
|
||||
* if numerator is also negative this becomes a positive frac
|
||||
*/
|
||||
if self.1 < 0 {
|
||||
d = 0 - self.1;
|
||||
n = 0 - self.0;
|
||||
}
|
||||
|
||||
/* divide both by their greatest common divisor
|
||||
* leading to simplest possible form
|
||||
*/
|
||||
let g = gcd(n, d);
|
||||
Fraction(n / g, d / g)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Fraction {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
let part: usize;
|
||||
if let Some(idx) = value.find('/') {
|
||||
part = idx;
|
||||
} else {
|
||||
return Err(E_NO_DENOMINATOR)
|
||||
}
|
||||
|
||||
// make sure there is ONLY ONE slash
|
||||
if let Some(idx) = value.rfind('/') && idx != part {
|
||||
return Err(E_MULTI_DENOMINATOR)
|
||||
}
|
||||
|
||||
let numerator_text = &value[..part];
|
||||
let denominator_text = &value[part+1..];
|
||||
|
||||
let numerator = numerator_text.parse::<isize>()
|
||||
.or(Err(E_NUMERATOR_PARSE_FAIL))?;
|
||||
let denominator = denominator_text.parse::<isize>()
|
||||
.or(Err(E_DENOMINATOR_PARSE_FAIL))?;
|
||||
|
||||
if denominator == 0 {
|
||||
return Err(E_ZERO_DENOMINATOR)
|
||||
}
|
||||
|
||||
Ok(Fraction(numerator, denominator))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Fraction {
|
||||
fn into(self) -> String {
|
||||
format!("{}/{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for Fraction {
|
||||
fn is_exact(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn make_inexact(&self) -> Float {
|
||||
Float(self.0 as f64 / self.1 as f64)
|
||||
}
|
||||
|
||||
fn make_exact(&self) -> Fraction {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Float {
|
||||
type Err = &'static str;
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Float(value.parse::<f64>().or(Err(E_FLOAT_PARSE_FAIL))?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Float {
|
||||
fn into(self) -> String {
|
||||
if self.is_exact() {
|
||||
format!("{}", self.0)
|
||||
} else {
|
||||
format!("#i{}", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for Float {
|
||||
fn is_exact(&self) -> bool {
|
||||
self.0.fract() == 0.0
|
||||
}
|
||||
|
||||
fn make_inexact(&self) -> Float {
|
||||
*self
|
||||
}
|
||||
|
||||
fn make_exact(&self) -> Fraction {
|
||||
if self.0.fract() == 0.0 {
|
||||
Fraction(self.0 as isize, 1)
|
||||
} else {
|
||||
todo!("rational approximation implementation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ScientificNotation {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
let part: usize;
|
||||
if let Some(idx) = value.find('e') {
|
||||
part = idx;
|
||||
} else {
|
||||
return Err(E_SCIENTIFIC_E)
|
||||
}
|
||||
|
||||
// make sure there is ONLY ONE slash
|
||||
if let Some(idx) = value.rfind('e') && idx != part {
|
||||
return Err(E_SCIENTIFIC_MULTI_E)
|
||||
}
|
||||
|
||||
let operand_text = &value[..part];
|
||||
let power_text = &value[part+1..];
|
||||
|
||||
let operand = operand_text.parse::<f32>()
|
||||
.or(Err(E_SCIENTIFIC_OPERAND))?;
|
||||
let power = power_text.parse::<isize>()
|
||||
.or(Err(E_SCIENTIFIC_POWER))?;
|
||||
|
||||
Ok(ScientificNotation(operand, power))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for ScientificNotation {
|
||||
fn into(self) -> String {
|
||||
format!("{}e{}", self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for ScientificNotation {
|
||||
fn is_exact(&self) -> bool {
|
||||
self.0.fract() == 0.0 && self.1 >= 0
|
||||
}
|
||||
|
||||
fn make_inexact(&self) -> Float {
|
||||
Float(self.0 as f64 * f64::powi(10.0, self.1 as i32) as f64)
|
||||
}
|
||||
|
||||
fn make_exact(&self) -> Fraction {
|
||||
self.make_inexact().make_exact()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_fraction_tests() {
|
||||
assert_eq!("2/3".parse::<Fraction>(),
|
||||
Ok(Fraction(2, 3)));
|
||||
|
||||
assert_eq!("0/1".parse::<Fraction>(),
|
||||
Ok(Fraction(0, 1)));
|
||||
|
||||
assert_eq!("-1/34".parse::<Fraction>(),
|
||||
Ok(Fraction(-1, 34)));
|
||||
|
||||
assert_eq!("2".parse::<Fraction>(),
|
||||
Err(E_NO_DENOMINATOR));
|
||||
|
||||
assert_eq!("2/2/2".parse::<Fraction>(),
|
||||
Err(E_MULTI_DENOMINATOR));
|
||||
|
||||
assert_eq!("2/0".parse::<Fraction>(),
|
||||
Err(E_ZERO_DENOMINATOR));
|
||||
|
||||
assert_eq!("3.3/3".parse::<Fraction>(),
|
||||
Err(E_NUMERATOR_PARSE_FAIL));
|
||||
|
||||
assert_eq!("2/two".parse::<Fraction>(),
|
||||
Err(E_DENOMINATOR_PARSE_FAIL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_scientific_notation_tests() {
|
||||
assert_eq!("2e3".parse::<ScientificNotation>(),
|
||||
Ok(ScientificNotation(2.0, 3)));
|
||||
|
||||
assert_eq!("0e1".parse::<ScientificNotation>(),
|
||||
Ok(ScientificNotation(0.0, 1)));
|
||||
|
||||
assert_eq!("-1e34".parse::<ScientificNotation>(),
|
||||
Ok(ScientificNotation(-1.0, 34)));
|
||||
|
||||
assert_eq!("3.3e3".parse::<ScientificNotation>(),
|
||||
Ok(ScientificNotation(3.3, 3)));
|
||||
|
||||
assert_eq!("2".parse::<ScientificNotation>(),
|
||||
Err(E_SCIENTIFIC_E));
|
||||
|
||||
assert_eq!("2e2e2".parse::<ScientificNotation>(),
|
||||
Err(E_SCIENTIFIC_MULTI_E));
|
||||
|
||||
assert_eq!("2etwo".parse::<ScientificNotation>(),
|
||||
Err(E_SCIENTIFIC_POWER));
|
||||
|
||||
assert_eq!("twoe2".parse::<ScientificNotation>(),
|
||||
Err(E_SCIENTIFIC_OPERAND));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_number_tests() {
|
||||
assert_eq!("1.3".parse::<Number>(),
|
||||
Ok(Number::Flt(Float(1.3))));
|
||||
|
||||
assert_eq!("1".parse::<Number>(),
|
||||
Ok(Number::Flt(Float(1 as f64))));
|
||||
|
||||
assert_eq!("1.3e3".parse::<Number>(),
|
||||
Ok(Number::Sci(ScientificNotation(1.3, 3))));
|
||||
|
||||
assert_eq!("+1.3".parse::<Number>(),
|
||||
Ok(Number::Flt(Float(1.3))));
|
||||
|
||||
assert_eq!("-1.3".parse::<Number>(),
|
||||
Ok(Number::Flt(Float(-1.3))));
|
||||
|
||||
assert_eq!("#d234".parse::<Number>(),
|
||||
Ok(Number::Flt(Float(234.0))));
|
||||
|
||||
assert_eq!("#o17".parse::<Number>(),
|
||||
Ok(Number::Fra(Fraction(15, 1))));
|
||||
|
||||
assert_eq!("#xAA".parse::<Number>(),
|
||||
Ok(Number::Fra(Fraction(170, 1))));
|
||||
|
||||
assert_eq!("#b101".parse::<Number>(),
|
||||
Ok(Number::Flt(Float(5.0))));
|
||||
|
||||
assert_eq!("2/4".parse::<Number>(),
|
||||
Ok(Number::Fra(Fraction(2, 4))));
|
||||
|
||||
assert_eq!("#e1/5".parse::<Number>(),
|
||||
Ok(Number::Fra(Fraction(1, 5))));
|
||||
|
||||
assert_eq!("#i1/5".parse::<Number>(),
|
||||
Ok(Number::Flt(Float(0.2))));
|
||||
|
||||
assert_eq!("#e1e1".parse::<Number>(),
|
||||
Ok(Number::Sci(ScientificNotation(1.0, 1))));
|
||||
|
||||
assert_eq!("+inf.0".parse::<Number>(),
|
||||
Ok(Number::Sym(SymbolicNumber::Inf)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_addition_subtraction_cases() {
|
||||
let cases = vec![
|
||||
vec!["1/5", "4/5", "1/1"],
|
||||
vec!["1/5", "0.8", "1/1"],
|
||||
vec!["1e1", "2.0", "12/1"],
|
||||
vec!["1e1", "2/1", "12/1"],
|
||||
vec!["1e1", "1/2", "10.5"],
|
||||
];
|
||||
|
||||
cases.iter().for_each(|case| {
|
||||
println!("+ {:#?}", case);
|
||||
let x = case[0].parse::<Number>().unwrap();
|
||||
let y = case[1].parse::<Number>().unwrap();
|
||||
let z = case[2].parse::<Number>().unwrap();
|
||||
|
||||
// test some mathematical properties
|
||||
assert_eq!(x + y, z);
|
||||
assert_eq!(x + y, y + x);
|
||||
assert_eq!(z - x, y);
|
||||
assert_eq!(x + y - x, y);
|
||||
});
|
||||
|
||||
// theres no reason this should adhere to all the other rules
|
||||
let x = "+inf.0".parse::<Number>().unwrap();
|
||||
let y = "1e1".parse::<Number>().unwrap();
|
||||
let z = "+inf.0".parse::<Number>().unwrap();
|
||||
assert_eq!(x + y, z);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_multiplication_division_cases() {
|
||||
let cases = vec![
|
||||
vec!["1/5", "5e0", "1/1"],
|
||||
vec!["1/5", "5", "1/1"],
|
||||
vec!["1/5", "2/1", "2/5"],
|
||||
vec!["4.4", "1/2", "2.2"],
|
||||
vec!["12.0", "1/2", "6/1"],
|
||||
vec!["1e1", "2.0", "20/1"],
|
||||
vec!["1e1", "2/1", "20/1"],
|
||||
vec!["1e1", "1/2", "5/1"],
|
||||
];
|
||||
|
||||
cases.iter().for_each(|case| {
|
||||
println!("+ {:#?}", case);
|
||||
let x = case[0].parse::<Number>().unwrap();
|
||||
let y = case[1].parse::<Number>().unwrap();
|
||||
let z = case[2].parse::<Number>().unwrap();
|
||||
|
||||
// test some mathematical properties
|
||||
assert_eq!(x * y, z);
|
||||
assert_eq!(x * y, y * x);
|
||||
assert_eq!(z / x, y);
|
||||
assert_eq!(x * y / x, y);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_pow_cases() {
|
||||
// TODO: add scientific notation cases
|
||||
let cases = vec![
|
||||
vec!["2", "2", "4"],
|
||||
vec!["2/1", "2/1", "4/1"],
|
||||
vec!["2/1", "2/-1", "1/4"],
|
||||
vec!["2/1", "2/2", "2/1"],
|
||||
vec!["2/1", "2.0", "4/1"],
|
||||
vec!["27/8", "2/-3", "4/9"]
|
||||
];
|
||||
|
||||
cases.iter().for_each(|case| {
|
||||
println!("+ {:#?}", case);
|
||||
let x = case[0].parse::<Number>().unwrap();
|
||||
let y = case[1].parse::<Number>().unwrap();
|
||||
let z = case[2].parse::<Number>().unwrap();
|
||||
assert_eq!(x.pow(y), z);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number_ord_cases() {
|
||||
// TODO: add more cases
|
||||
let cases = vec![
|
||||
vec!["1/2", "1.0", "1e1"],
|
||||
];
|
||||
|
||||
cases.iter().for_each(|case| {
|
||||
println!("+ {:#?}", case);
|
||||
let x = case[0].parse::<Number>().unwrap();
|
||||
let y = case[1].parse::<Number>().unwrap();
|
||||
let z = case[2].parse::<Number>().unwrap();
|
||||
assert!(x < y);
|
||||
assert!(y < z);
|
||||
assert!(x < z);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_negative_exponent_case() {
|
||||
if let Float(0.1) = "1e-1"
|
||||
.parse::<Number>()
|
||||
.unwrap()
|
||||
.make_inexact() {
|
||||
return
|
||||
}
|
||||
|
||||
assert!(false)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue