All checks were successful
per-push tests / build (push) Successful in 1m52s
per-push tests / test-utility (push) Successful in 58s
per-push tests / test-frontend (push) Successful in 1m52s
per-push tests / test-backend (push) Successful in 1m0s
per-push tests / timed-decomposer-parse (push) Successful in 1m2s
Additionally: make release target binaries smaller and faster Signed-off-by: Ava Affine <ava@sunnypup.io>
913 lines
27 KiB
Rust
913 lines
27 KiB
Rust
/* 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 alloc::vec;
|
|
use alloc::vec::Vec;
|
|
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 Default for Number {
|
|
fn default() -> Self {
|
|
Number::Fra(Fraction(0, 1))
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// this looks rushed and it is
|
|
// would rather work on organelles replacement than improve this
|
|
impl Into<Vec<u8>> for Number {
|
|
fn into(self) -> Vec<u8> {
|
|
let mut out = vec![];
|
|
match self {
|
|
Number::Sci(num) => {
|
|
out.push(0x00);
|
|
for ele in num.0.to_be_bytes().iter() {
|
|
out.push(*ele);
|
|
}
|
|
for ele in num.1.to_be_bytes().iter() {
|
|
out.push(*ele);
|
|
}
|
|
out
|
|
},
|
|
Number::Flt(num) => {
|
|
out.push(0x01 as u8);
|
|
for ele in num.0.to_be_bytes().iter() {
|
|
out.push(*ele);
|
|
}
|
|
out
|
|
},
|
|
Number::Fra(num) => {
|
|
out.push(0x02);
|
|
for ele in num.0.to_be_bytes().iter() {
|
|
out.push(*ele);
|
|
}
|
|
for ele in num.1.to_be_bytes().iter() {
|
|
out.push(*ele);
|
|
}
|
|
out
|
|
},
|
|
Number::Sym(num) => {
|
|
match num {
|
|
SymbolicNumber::Inf => out.push(0x03),
|
|
SymbolicNumber::NaN => out.push(0x04),
|
|
SymbolicNumber::NegInf => out.push(0x05),
|
|
SymbolicNumber::NegNan => out.push(0x06),
|
|
}
|
|
out
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// same as the Into impl
|
|
impl TryFrom<&[u8]> for Number {
|
|
type Error = &'static str;
|
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
|
match value[0] {
|
|
0x03 => Ok(Number::Sym(SymbolicNumber::Inf)),
|
|
0x04 => Ok(Number::Sym(SymbolicNumber::NaN)),
|
|
0x05 => Ok(Number::Sym(SymbolicNumber::NegInf)),
|
|
0x06 => Ok(Number::Sym(SymbolicNumber::NegNan)),
|
|
0x00 if value.len() >= (1 + 4 + (isize::BITS / 8)) as usize => {
|
|
let mut i: [u8; 4] = [0, 0, 0, 0];
|
|
value[1..5].iter().zip(i.iter_mut())
|
|
.for_each(|(a, b)| { *b = *a });
|
|
let i = f32::from_be_bytes(i);
|
|
|
|
let mut j: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
value[5..13].iter().zip(j.iter_mut())
|
|
.for_each(|(a, b)| { *b = *a });
|
|
let j = isize::from_be_bytes(j);
|
|
Ok(Number::Sci(ScientificNotation(i, j)))
|
|
},
|
|
0x01 if value.len() >= 9 as usize => {
|
|
let mut i: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
value[1..9].iter().zip(i.iter_mut())
|
|
.for_each(|(a, b)| { *b = *a });
|
|
let i = f64::from_be_bytes(i);
|
|
Ok(Number::Flt(Float(i)))
|
|
},
|
|
0x02 if value.len() >= 1 + ((isize::BITS / 8) * 2) as usize => {
|
|
let mut i: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
value[1..9].iter().zip(i.iter_mut())
|
|
.for_each(|(a, b)| { *b = *a });
|
|
let i = isize::from_be_bytes(i);
|
|
let mut j: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
value[9..17].iter().zip(j.iter_mut())
|
|
.for_each(|(a, b)| { *b = *a });
|
|
let j = isize::from_be_bytes(j);
|
|
Ok(Number::Fra(Fraction(i, j)))
|
|
},
|
|
_ => Err("attempted to deserialize invalid number format")
|
|
}
|
|
}
|
|
}
|
|
|
|
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 serialize_deserialize_tests() {
|
|
let cases = vec![
|
|
"2/3".parse::<Number>().unwrap(),
|
|
"-4/5".parse::<Number>().unwrap(),
|
|
"2e45".parse::<Number>().unwrap(),
|
|
"1.2432566".parse::<Number>().unwrap(),
|
|
"+inf.0".parse::<Number>().unwrap(),
|
|
];
|
|
|
|
for i in cases.iter() {
|
|
let j = Into::<Vec<u8>>::into(*i);
|
|
assert_eq!(*i, Number::try_from(j.as_slice()).unwrap());
|
|
}
|
|
}
|
|
|
|
#[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)
|
|
}
|
|
}
|