From d9345b8583871315b20c26a3da365b210c1624cb Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Wed, 11 Dec 2024 13:12:37 +0100 Subject: [PATCH 1/3] reimplement div --- src/interpreter/int.rs | 12 ++ src/interpreter/num.rs | 360 +++++++++++++++++++---------------------- 2 files changed, 180 insertions(+), 192 deletions(-) diff --git a/src/interpreter/int.rs b/src/interpreter/int.rs index c4eccbe..2d9545b 100644 --- a/src/interpreter/int.rs +++ b/src/interpreter/int.rs @@ -411,8 +411,20 @@ impl From for BigRational { } } +impl From<&Int> for BigRational { + fn from(value: &Int) -> Self { + Self::from(value.to_bigint()) + } +} + impl From for Complex64 { fn from(value: Int) -> Self { + Complex64::from(&value) + } +} + +impl From<&Int> for Complex64 { + fn from(value: &Int) -> Self { match value { Int::Int64(i) => Self::from(i.to_f64().unwrap_or(f64::INFINITY)), Int::BigInt(i) => Self::from(i.to_f64().unwrap_or(f64::INFINITY)), diff --git a/src/interpreter/num.rs b/src/interpreter/num.rs index 93bb7c5..0712607 100644 --- a/src/interpreter/num.rs +++ b/src/interpreter/num.rs @@ -193,216 +193,152 @@ impl Not for Number { } } -impl Add for Number { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - match (self, rhs) { - // Operands are the same - (Self::Int(i1), Self::Int(i2)) => Self::Int(i1 + i2), - (Self::Float(f1), Self::Float(f2)) => Self::Float(f1.add(f2)), - (Self::Rational(r1), Self::Rational(r2)) => { - Self::Rational(Box::new(Add::add(*r1, *r2))) - } - (Self::Complex(c1), Self::Complex(c2)) => Self::Complex(c1 + c2), - - // Float vs other - (Self::Float(p1), Self::Int(p2)) => Self::Float(p1.add(f64::from(p2))), - (Self::Float(p1), Self::Rational(p2)) => Self::Float(p1.add(rational_to_float(&p2))), - (Self::Float(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).add(p2)), - - // Int vs other - (Self::Int(p1), Self::Float(p2)) => Self::Float(f64::from(p1).add(p2)), - (Self::Int(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Add::add(BigRational::from(p1), *p2))) - } - (Self::Int(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).add(p2)), - - // Rational vs other - (Self::Rational(p1), Self::Int(p2)) => { - Self::Rational(Box::new(Add::add(*p1, BigRational::from(p2)))) - } - (Self::Rational(p1), Self::Float(p2)) => Self::Float(rational_to_float(&p1).add(p2)), - (Self::Rational(p1), Self::Complex(p2)) => { - Self::Complex(rational_to_complex(&p1).add(p2)) - } +trait Unbox { + type Output; + fn unbox(self) -> Self::Output; +} - // Complex vs other - (Self::Complex(p1), Self::Int(p2)) => Self::Complex(Complex::from(p2).add(p1)), - (Self::Complex(p1), Self::Rational(p2)) => { - Self::Complex(Complex::from(p2.to_f64().unwrap_or(f64::NAN)).add(p1)) - } - (Self::Complex(p1), Self::Float(p2)) => Self::Complex(Complex::from(p2).add(p1)), - } +impl Unbox for Box { + type Output = BigRational; + fn unbox(self) -> Self::Output { + *self } } -impl Sub for Number { - type Output = Self; - fn sub(self, rhs: Self) -> Self::Output { - match (self, rhs) { - // Operands are the same - (Self::Int(i1), Self::Int(i2)) => Self::Int(i1 - i2), - (Self::Float(f1), Self::Float(f2)) => Self::Float(f1.sub(f2)), - (Self::Rational(r1), Self::Rational(r2)) => { - Self::Rational(Box::new(Sub::sub(*r1, *r2))) - } - (Self::Complex(c1), Self::Complex(c2)) => Self::Complex(c1 - c2), - - // Float vs other - (Self::Float(p1), Self::Int(p2)) => Self::Float(p1.sub(f64::from(p2))), - (Self::Float(p1), Self::Rational(p2)) => Self::Float(p1.sub(rational_to_float(&p2))), - (Self::Float(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).sub(p2)), - - // Int vs other - (Self::Int(p1), Self::Float(p2)) => Self::Float(f64::from(p1).sub(p2)), - (Self::Int(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Sub::sub(BigRational::from(p1), *p2))) - } - (Self::Int(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).sub(p2)), - - // Rational vs Other - (Self::Rational(p1), Self::Int(p2)) => { - Self::Rational(Box::new(Sub::sub(*p1, BigRational::from(p2)))) - } - (Self::Rational(p1), Self::Float(p2)) => Self::Float(rational_to_float(&p1).sub(p2)), - (Self::Rational(p1), Self::Complex(p2)) => { - Self::Complex(Complex::from(p1.to_f64().unwrap_or(f64::NAN)).sub(p2)) - //TODO: Check if this is logical - } - - // Complex vs Other - (Self::Complex(p1), Self::Int(p2)) => Self::Complex(Complex::from(p2).sub(p1)), - (Self::Complex(p1), Self::Rational(p2)) => { - Self::Complex(Complex::from(p2.to_f64().unwrap_or(f64::NAN)).sub(p1)) - } - (Self::Complex(p1), Self::Float(p2)) => Self::Complex(Complex::from(p2).sub(p1)), - } +impl<'a> Unbox for &'a Box { + type Output = &'a BigRational; + fn unbox(self) -> Self::Output { + &**self } } -impl Div for Number { - type Output = Self; - - fn div(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Self::Int(ref p1), Self::Int(p2)) => { - if p1.is_zero() && p2.is_zero() { - return Self::Float(f64::NAN); - } else if p2.is_zero() { - return Self::Float(f64::INFINITY); - } else if p1.rem(&p2) == 0i32.into() { - return Self::Int(p1.div(&p2)); +macro_rules! impl_binary_operator { + ($self:ty, $other:ty, $trait:ident, $method:ident,$intmethod:expr,$floatmethod:expr,$rationalmethod:expr,$complexmethod:expr) => { + impl $trait<$other> for $self { + type Output = Number; + fn $method(self, other: $other) -> Number { + match (self, other) { + // Complex + (Number::Complex(left), right) => { + Number::Complex($complexmethod(left, right.to_complex())) + } + (left, Number::Complex(right)) => { + Number::Complex($complexmethod(left.to_complex(), right)) + } + // Float + // NOTE: these `expect` calls are safe because complex has already been handled + (Number::Float(left), right) => Number::Float($floatmethod( + left, + right.to_f64().expect("cannot convert complex to float"), + )), + (left, Number::Float(right)) => Number::Float($floatmethod( + left.to_f64().expect("cannot convert complex to float"), + right, + )), + // Rational + // NOTE: these `expect` calls are safe because complex and float are handled + (left, Number::Rational(right)) => Number::rational($rationalmethod( + left.to_rational().expect("cannot convert to rational"), + right.unbox(), + )), + (Number::Rational(left), right) => Number::rational($rationalmethod( + left.unbox(), + right.to_rational().expect("cannot convert to rational"), + )), + // Integer + (Number::Int(left), Number::Int(right)) => { + // TODO: is this double to owned retard? + Number::Int($intmethod(left.to_owned(), right.to_owned())) + } } - BigRational::new(p1.into(), p2.into()).into() - } - (Self::Float(p1), Self::Float(p2)) => Self::Float(p1 / p2), - (Self::Rational(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Div::div(*p1, *p2))) } - (Self::Complex(p1), Self::Complex(p2)) => Self::Complex(p1 / p2), + } + }; +} - // Float vs other - (Self::Float(p1), Self::Int(p2)) => Self::Float(p1.div(f64::from(p2))), - (Self::Float(p1), Self::Rational(p2)) => Self::Float(p1.div(rational_to_float(&p2))), - (Self::Float(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).div(p2)), +macro_rules! impl_binary_operator_all { + ($implement:ident,$method:ident,$intmethod:expr,$floatmethod:expr,$rationalmethod:expr,$complexmethod:expr) => { + impl_binary_operator!( + Number, + Number, + $implement, + $method, + $intmethod, + $floatmethod, + $rationalmethod, + $complexmethod + ); + impl_binary_operator!( + Number, + &Number, + $implement, + $method, + $intmethod, + $floatmethod, + $rationalmethod, + $complexmethod + ); + impl_binary_operator!( + &Number, + Number, + $implement, + $method, + $intmethod, + $floatmethod, + $rationalmethod, + $complexmethod + ); + impl_binary_operator!( + &Number, + &Number, + $implement, + $method, + $intmethod, + $floatmethod, + $rationalmethod, + $complexmethod + ); + }; +} - // Int vs other - (Self::Int(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Div::div(BigRational::from(p1), *p2))) - } - (Self::Int(p1), Self::Float(p2)) => Self::Float(f64::from(p1).div(p2)), - (Self::Int(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).div(p2)), +impl_binary_operator_all!(Add, add, Add::add, Add::add, Add::add, Add::add); +impl_binary_operator_all!(Sub, sub, Sub::sub, Sub::sub, Sub::sub, Sub::sub); +impl_binary_operator_all!(Mul, mul, Mul::mul, Mul::mul, Mul::mul, Mul::mul); +impl_binary_operator_all!(Rem, rem, Rem::rem, Rem::rem, Rem::rem, Rem::rem); - // Rational vs other - (Self::Rational(p1), Self::Int(p2)) => { - Self::Rational(Box::new(Div::div(*p1, BigRational::from(p2)))) - } - (Self::Rational(p1), Self::Float(p2)) => Self::Float(rational_to_float(&p1).div(p2)), - (Self::Rational(p1), Self::Complex(p2)) => { - Self::Complex(Complex::from(p1.to_f64().unwrap_or(f64::NAN)).div(p2)) - //TODO: Check if this is logical - } +impl Div<&Number> for &Number { + type Output = Number; - // Complex vs Other - (Self::Complex(p1), Self::Int(p2)) => Self::Complex(Complex::from(p2).div(p1)), - (Self::Complex(p1), Self::Rational(p2)) => { - Self::Complex(Complex::from(p2.to_f64().unwrap_or(f64::NAN)).div(p1)) - } - (Self::Complex(p1), Self::Float(p2)) => Self::Complex(Complex::from(p2).div(p1)), + fn div(self, rhs: &Number) -> Self::Output { + match (self.to_rational(), rhs.to_rational()) { + (Some(left), Some(right)) if !right.is_zero() => Number::rational(left / right), + _ => match (self.to_f64(), rhs.to_f64()) { + (Some(left), Some(right)) => Number::Float(left / right), + _ => Number::Complex(self.to_complex() / rhs.to_complex()), + }, } } } -impl Mul for Number { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Self::Int(p1), Self::Int(p2)) => Self::Int(p1 * p2), - (Self::Float(p1), Self::Float(p2)) => Self::Float(p1 * p2), - (Self::Rational(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Mul::mul(*p1, *p2))) - } - (Self::Complex(p1), Self::Complex(p2)) => Self::Complex(p1 * p2), - - // Float vs other - (Self::Float(p1), Self::Int(p2)) => Self::Float(p1.mul(f64::from(p2))), - (Self::Float(p1), Self::Rational(p2)) => Self::Float(p1.mul(rational_to_float(&p2))), - (Self::Float(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).mul(p2)), - - // Int vs other - (Self::Int(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Mul::mul(BigRational::from(p1), *p2))) - } - (Self::Int(p1), Self::Float(p2)) => Self::Float(f64::from(p1).mul(p2)), - (Self::Int(p1), Self::Complex(p2)) => Self::Complex(Complex::from(p1).mul(p2)), - - // Rational vs other - (Self::Rational(p1), Self::Float(p2)) => Self::Float(rational_to_float(&p1).mul(p2)), - (Self::Rational(p1), Self::Int(p2)) => { - Self::Rational(Box::new(Mul::mul(*p1, BigRational::from(p2)))) - } - (Self::Rational(p1), Self::Complex(p2)) => { - Self::Complex(Complex::from(p1.to_f64().unwrap_or(f64::NAN)).div(p2)) - } +impl Div for Number { + type Output = Number; - // Complex vs Other - (Self::Complex(p1), Self::Int(p2)) => Self::Complex(Complex::from(p2).mul(p1)), - (Self::Complex(p1), Self::Rational(p2)) => { - Self::Complex(Complex::from(p2.to_f64().unwrap_or(f64::NAN)).mul(p1)) - } - (Self::Complex(p1), Self::Float(p2)) => Self::Complex(Complex::from(p2).mul(p1)), - } + fn div(self, rhs: Number) -> Self::Output { + &self / &rhs } } +impl Div<&Number> for Number { + type Output = Number; -impl Rem for Number { - type Output = Self; - - fn rem(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Self::Int(p1), Self::Int(p2)) => Self::Int(p1.rem(p2)), - (Self::Float(p1), Self::Float(p2)) => Self::Float(p1.rem(p2)), - (Self::Rational(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Rem::rem(*p1, *p2))) - } - (Self::Complex(p1), Self::Complex(p2)) => Self::Complex(p1.rem(p2)), + fn div(self, rhs: &Number) -> Self::Output { + &self / rhs + } +} +impl Div for &Number { + type Output = Number; - // Rational vs Int - (Self::Rational(p1), Self::Int(p2)) => { - Self::Rational(Box::new(Rem::rem(*p1, BigRational::from(p2)))) - } - (Self::Int(p1), Self::Rational(p2)) => { - Self::Rational(Box::new(Rem::rem(BigRational::from(p1), *p2))) - } - // TODO: implement other cases - (a, b) => panic!( - "remainder between {} and {} is not implemented", - a.type_name(), - b.type_name() - ), - } + fn div(self, rhs: Number) -> Self::Output { + self / &rhs } } @@ -415,6 +351,22 @@ pub enum EuclideanDivisionError { } impl Number { + #[must_use] + pub fn complex(re: f64, im: f64) -> Self { + Self::Complex(Complex64 { re, im }) + } + + #[must_use] + pub fn float(f: f64) -> Self { + Self::Float(f) + } + + #[must_use] + pub fn rational(rat: BigRational) -> Self { + Self::Rational(Box::new(rat)) + } + + // TODO: change this to &'static str fn type_name(&self) -> String { match self { Self::Int(_) => "int".to_string(), @@ -539,11 +491,35 @@ impl Number { Ok(n) } + pub fn to_complex(&self) -> Complex64 { + match self { + Number::Int(i) => Complex64::from(i), + Number::Float(f) => Complex64::from(f), + Number::Rational(r) => rational_to_complex(&r), + Number::Complex(c) => *c, + } + } + + pub fn to_f64(&self) -> Option { + match self { + Number::Int(i) => Some(f64::from(i)), + Number::Float(f) => Some(*f), + Number::Rational(r) => Some(rational_to_float(r)), + Number::Complex(_) => None, + } + } + + pub fn to_rational(&self) -> Option { + match self { + Number::Int(i) => Some(BigRational::from(i)), + Number::Float(_) => None, + Number::Rational(r) => Some(BigRational::clone(&**r)), + Number::Complex(_) => None, + } + } + /// Converts this number into a real (complex) number with the imaginary part set to 0.0 - /// which makes it esy to do comparison on all numbers (and possibly other things) - /// - /// TODO: in the future we might want to create a new Real type which is the sum of Int and Float - /// in order to better handle `BigInt`s which now lose tons of precision and will yield incorrect results + /// which makes it easy to do comparison on all numbers (and possibly other things) #[must_use] pub fn to_reals(&self) -> (RealNumber, RealNumber) { match self { From 2dd725300437dd984f2185a690c40a0ec53e35f0 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Thu, 19 Dec 2024 22:59:52 +0100 Subject: [PATCH 2/3] remove some useless number cloning --- src/interpreter/int.rs | 16 ++++++++++++++++ src/interpreter/num.rs | 5 +---- src/stdlib/math.rs | 5 ++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/interpreter/int.rs b/src/interpreter/int.rs index 2d9545b..aeb1095 100644 --- a/src/interpreter/int.rs +++ b/src/interpreter/int.rs @@ -257,6 +257,22 @@ macro_rules! impl_binary_operator { Int::BigInt(self.to_bigint().$method(rhs.to_bigint())) } } + impl std::ops::$trait for &Int { + type Output = Int; + + fn $method(self, rhs: Int) -> Self::Output { + match (&self, &rhs) { + (Int::Int64(p1), Int::Int64(p2)) => { + if let Some(s) = p1.$safe_method(*p2) { + Int::Int64(s); + } + } + _ => {} + } + + Int::BigInt(self.to_bigint().$method(rhs.to_bigint())) + } + } impl std::ops::$trait<&Int> for &Int { type Output = Int; diff --git a/src/interpreter/num.rs b/src/interpreter/num.rs index 0712607..c706d6e 100644 --- a/src/interpreter/num.rs +++ b/src/interpreter/num.rs @@ -246,10 +246,7 @@ macro_rules! impl_binary_operator { right.to_rational().expect("cannot convert to rational"), )), // Integer - (Number::Int(left), Number::Int(right)) => { - // TODO: is this double to owned retard? - Number::Int($intmethod(left.to_owned(), right.to_owned())) - } + (Number::Int(left), Number::Int(right)) => Number::Int($intmethod(left, right)), } } } diff --git a/src/stdlib/math.rs b/src/stdlib/math.rs index 34ad08d..9382178 100644 --- a/src/stdlib/math.rs +++ b/src/stdlib/math.rs @@ -17,7 +17,7 @@ where { fn try_sum(&mut self) -> anyhow::Result { self.try_fold(Number::from(0), |acc, cur| match cur.borrow() { - Value::Number(n) => Ok(acc + n.clone()), + Value::Number(n) => Ok(acc + n), value => Err(anyhow::anyhow!( "cannot sum {} and number", value.value_type() @@ -37,8 +37,7 @@ where { fn try_product(&mut self) -> anyhow::Result { self.try_fold(Number::from(1), |acc, cur| match cur.borrow() { - // TODO: remove this clone once we can do math with references - Value::Number(n) => Ok(acc * n.clone()), + Value::Number(n) => Ok(acc * n), value => Err(anyhow::anyhow!( "cannot multiply {} and number", value.value_type() From dea1a170cf8e83d7407a0ba6a60f51b9489d3f73 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Tue, 4 Mar 2025 09:12:58 +0100 Subject: [PATCH 3/3] Add random utilities and a new benchmark that uses it --- Cargo.lock | 77 +++++++++++++++++++++++++++------ Cargo.toml | 4 +- andy-cpp-macros/src/function.rs | 2 +- benches/programs/pi_approx.ndc | 14 ++++++ src/interpreter/environment.rs | 1 + src/interpreter/num.rs | 58 ++++++++++++++++++++++--- src/interpreter/value.rs | 19 +++++++- src/stdlib.rs | 1 + src/stdlib/rand.rs | 57 ++++++++++++++++++++++++ 9 files changed, 211 insertions(+), 22 deletions(-) create mode 100644 benches/programs/pi_approx.ndc create mode 100644 src/stdlib/rand.rs diff --git a/Cargo.lock b/Cargo.lock index e05c6db..02f61d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,10 +24,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -383,7 +383,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -751,7 +763,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -810,20 +822,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ - "libc", "rand_chacha", "rand_core", + "zerocopy 0.8.15", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -831,11 +843,12 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" dependencies = [ - "getrandom", + "getrandom 0.3.1", + "zerocopy 0.8.15", ] [[package]] @@ -1171,6 +1184,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1330,6 +1352,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -1337,7 +1368,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e101d4bc320b6f9abb68846837b70e25e380ca2f467ab494bf29fcc435fcc3" +dependencies = [ + "zerocopy-derive 0.8.15", ] [[package]] @@ -1350,3 +1390,14 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerocopy-derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a73df1008145cd135b3c780d275c57c3e6ba8324a41bd5e0008fe167c3bc7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index bd92d7b..74b9cb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,8 +32,8 @@ num = "0.4.1" once_cell = "1.20.2" ordered-float = "4.2.0" owo-colors = "4.1.0" -rand = "0.8.5" -rand_chacha = "0.3.1" +rand = "0.9.0" +rand_chacha = "0.9.0" regex = "1.10.4" rustyline = { version = "15.0.0", optional = true, features = ["derive"] } ryu = "1.0.17" diff --git a/andy-cpp-macros/src/function.rs b/andy-cpp-macros/src/function.rs index 1b77d25..bd573bf 100644 --- a/andy-cpp-macros/src/function.rs +++ b/andy-cpp-macros/src/function.rs @@ -117,7 +117,7 @@ fn wrap_single( }, ty @ syn::Type::Path(_) if path_ends_with(ty, "Result") => quote! { let value = result.map_err(|err| crate::interpreter::function::FunctionCarrier::IntoEvaluationError(Box::new(err)))?; - return Ok(Value::from(value)); + return Ok(crate::interpreter::value::Value::from(value)); }, _ => quote! { let result = crate::interpreter::value::Value::from(result); diff --git a/benches/programs/pi_approx.ndc b/benches/programs/pi_approx.ndc new file mode 100644 index 0000000..610daec --- /dev/null +++ b/benches/programs/pi_approx.ndc @@ -0,0 +1,14 @@ +fn monte_carlo_pi(num_samples) { + let inside_circle = 0; + + for _ in 0..num_samples { + let x, y = randf(-1, 1), randf(-1, 1); + if x ^ 2 + y ^ 2 <= 1 { + inside_circle += 1 + } + } + + return (inside_circle / num_samples) * 4; +} + +print(monte_carlo_pi(2_500_000).float) diff --git a/src/interpreter/environment.rs b/src/interpreter/environment.rs index 79eb710..69e1f0b 100644 --- a/src/interpreter/environment.rs +++ b/src/interpreter/environment.rs @@ -88,6 +88,7 @@ impl Environment { crate::stdlib::list::register(&mut env); crate::stdlib::math::f64::register(&mut env); crate::stdlib::math::register(&mut env); + crate::stdlib::rand::register(&mut env); crate::stdlib::regex::register(&mut env); crate::stdlib::sequence::extra::register(&mut env); crate::stdlib::sequence::register(&mut env); diff --git a/src/interpreter/num.rs b/src/interpreter/num.rs index c706d6e..b907754 100644 --- a/src/interpreter/num.rs +++ b/src/interpreter/num.rs @@ -218,6 +218,8 @@ macro_rules! impl_binary_operator { type Output = Number; fn $method(self, other: $other) -> Number { match (self, other) { + // Integer + (Number::Int(left), Number::Int(right)) => Number::Int($intmethod(left, right)), // Complex (Number::Complex(left), right) => { Number::Complex($complexmethod(left, right.to_complex())) @@ -245,8 +247,6 @@ macro_rules! impl_binary_operator { left.unbox(), right.to_rational().expect("cannot convert to rational"), )), - // Integer - (Number::Int(left), Number::Int(right)) => Number::Int($intmethod(left, right)), } } } @@ -306,6 +306,7 @@ impl_binary_operator_all!(Rem, rem, Rem::rem, Rem::rem, Rem::rem, Rem::rem); impl Div<&Number> for &Number { type Output = Number; + /// TODO: always converting operands to rational numbers is needlessly slow in some cases fn div(self, rhs: &Number) -> Self::Output { match (self.to_rational(), rhs.to_rational()) { (Some(left), Some(right)) if !right.is_zero() => Number::rational(left / right), @@ -488,15 +489,17 @@ impl Number { Ok(n) } + #[must_use] pub fn to_complex(&self) -> Complex64 { match self { Number::Int(i) => Complex64::from(i), Number::Float(f) => Complex64::from(f), - Number::Rational(r) => rational_to_complex(&r), + Number::Rational(r) => rational_to_complex(r), Number::Complex(c) => *c, } } + #[must_use] pub fn to_f64(&self) -> Option { match self { Number::Int(i) => Some(f64::from(i)), @@ -506,12 +509,12 @@ impl Number { } } + #[must_use] pub fn to_rational(&self) -> Option { match self { Number::Int(i) => Some(BigRational::from(i)), - Number::Float(_) => None, Number::Rational(r) => Some(BigRational::clone(&**r)), - Number::Complex(_) => None, + Number::Float(_) | Number::Complex(_) => None, } } @@ -614,6 +617,51 @@ impl TryFrom for usize { } } +#[derive(thiserror::Error, Debug)] +pub enum NumberToFloatError { + #[error("cannot convert {0} to float")] + UnsupportedType(NumberType), + #[error("cannot convert {0} to float")] + UnsupportedValue(Number), +} + +impl TryFrom<&Number> for f64 { + type Error = NumberToFloatError; + + fn try_from(value: &Number) -> Result { + match value { + Number::Int(Int::BigInt(bi)) => bi.to_f64(), + Number::Int(Int::Int64(i)) => i.to_f64(), + Number::Float(f) => Some(*f), + Number::Rational(r) => r.to_f64(), + _ => return Err(Self::Error::UnsupportedType(NumberType::from(value))), + } + .ok_or_else(|| Self::Error::UnsupportedValue(value.clone())) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum NumberToIntError { + #[error("cannot convert {0} to int")] + UnsupportedType(NumberType), + #[error("cannot convert {0} to int")] + UnsupportedValue(Number), +} + +impl TryFrom<&Number> for i64 { + type Error = NumberToIntError; + + fn try_from(value: &Number) -> Result { + match value { + Number::Int(Int::BigInt(bi)) => bi + .try_into() + .map_err(|_| NumberToIntError::UnsupportedValue(value.clone())), + Number::Int(Int::Int64(i)) => Ok(*i), + _ => Err(Self::Error::UnsupportedType(NumberType::from(value))), + } + } +} + impl fmt::Display for Number { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index fea63a8..b81f030 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -12,7 +12,7 @@ use crate::compare::FallibleOrd; use crate::hash_map::DefaultHasher; use crate::interpreter::function::{Function, OverloadedFunction}; use crate::interpreter::int::Int; -use crate::interpreter::num::{Number, NumberToUsizeError, NumberType}; +use crate::interpreter::num::{Number, NumberToFloatError, NumberToUsizeError, NumberType}; use crate::interpreter::sequence::Sequence; use super::iterator::{ValueIterator, ValueRange, ValueRangeFrom, ValueRangeInclusive}; @@ -432,6 +432,9 @@ pub enum ConversionError { #[error("{0}")] NumberToUsizeError(#[from] NumberToUsizeError), + + #[error("{0}")] + NumberToFloatError(#[from] NumberToFloatError), } /// `TryFrom` implementation to convert a `Sequence::Tuple` into (Value, Value) @@ -480,6 +483,20 @@ impl TryFrom for i64 { } } +impl TryFrom<&mut Value> for f64 { + type Error = ConversionError; + + fn try_from(value: &mut Value) -> Result { + match value { + Value::Number(n) => Ok((&*n).try_into()?), + v => Err(Self::Error::UnsupportedVariant( + v.value_type(), + stringify!(f64), + )), + } + } +} + impl TryFrom<&mut Value> for i64 { type Error = ConversionError; diff --git a/src/stdlib.rs b/src/stdlib.rs index 10e8bed..493a853 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -6,6 +6,7 @@ pub mod hash_map; pub mod heap; pub mod list; pub mod math; +pub mod rand; pub mod regex; pub mod sequence; pub mod serde; diff --git a/src/stdlib/rand.rs b/src/stdlib/rand.rs new file mode 100644 index 0000000..ece10e2 --- /dev/null +++ b/src/stdlib/rand.rs @@ -0,0 +1,57 @@ +use andy_cpp_macros::export_module; +use anyhow::Context; +use rand::distr::uniform::SampleUniform; +use rand::distr::Uniform; +use rand::Rng; + +pub fn random_n( + lower: N, + upper: N, +) -> anyhow::Result { + let mut rng = rand::rng(); + let side: Uniform = Uniform::new(lower, upper).context(format!( + "Lower bound ({lower}) cannot be greater than upper bound ({upper})." + ))?; + Ok(rng.sample(side)) +} + +#[export_module] +mod inner { + use crate::interpreter::num::Number; + + #[function(name = "randf")] + /// Generate a random number between 0 (inclusive) and 1 (exclusive) + pub fn randf_0() -> anyhow::Result { + random_n(0.0, 1.0) + } + + #[function(name = "randf")] + /// Generate a random number between 0 (inclusive) and `upper` (exclusive) + pub fn randf_1(upper: &Number) -> anyhow::Result { + random_n(0.0, upper.try_into()?) + } + + #[function(name = "randf")] + /// Generate a random number between `lower` (inclusive) and `upper` (exclusive) + pub fn randf_2(lower: &Number, upper: &Number) -> anyhow::Result { + random_n(lower.try_into()?, upper.try_into()?) + } + + #[function(name = "randi")] + /// Generate a random number between 0 (inclusive) and 1 (exclusive) + pub fn randi_0() -> anyhow::Result { + random_n(0, i64::MAX) + } + + #[function(name = "randi")] + /// Generate a random number between 0 (inclusive) and `upper` (exclusive) + pub fn randi_1(upper: &Number) -> anyhow::Result { + random_n(0, upper.try_into()?) + } + + #[function(name = "randi")] + /// Generate a random number between `lower` (inclusive) and `upper` (exclusive) + pub fn randi_2(lower: &Number, upper: &Number) -> anyhow::Result { + random_n(lower.try_into()?, upper.try_into()?) + } +}