Mycelium/mycelium/src/number.rs
Ava Affine 4ad319213d HyphaeVM - WIP
This commit is a WORK IN PROGRESS for the base implementation of the
HyphaeVM. This will be squashed into a larger commit eventually when
the work of implementing the HyphaeVM is finished.

Of note, the ISA is mostly finished and much of the VM design is in
place. Yet to be done are a few traps in mycelium, migrating pieces
like the number package and the sexpr package into the VM package,
and of course much testing.

Signed-off-by: Ava Affine <ava@sunnypup.io>
2025-07-24 19:26:31 +00:00

796 lines
23 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/>.
*/
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)
}
}