From f984b966f8413d90c4a59410eb5ccfdae3242a18 Mon Sep 17 00:00:00 2001 From: wucke13 Date: Wed, 8 Jun 2022 10:12:26 +0200 Subject: [PATCH 1/3] fix #283: add possibility to include num-traits/libm for sin and cos in no_std builds --- .github/workflows/ci-full-test-suite.yml | 2 +- Cargo.toml | 3 + src/features.rs | 12 +-- src/lib.rs | 5 +- src/si/angle.rs | 15 ++-- src/si/ratio.rs | 106 +++++++++++++++++++---- src/system.rs | 4 +- src/tests/quantity.rs | 11 ++- src/tests/system.rs | 21 +++-- 9 files changed, 134 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci-full-test-suite.yml b/.github/workflows/ci-full-test-suite.yml index 1003e3d4..a5a7e7b7 100644 --- a/.github/workflows/ci-full-test-suite.yml +++ b/.github/workflows/ci-full-test-suite.yml @@ -36,7 +36,7 @@ jobs: run: cargo test --verbose --no-default-features --features "f32 si" - name: Test all non-storage type features - run: cargo test --verbose --no-default-features --features "autoconvert f32 si use_serde" + run: cargo test --verbose --no-default-features --features "autoconvert f32 si use_serde libm" - name: Test si with underlying storage types run: cargo test --verbose --no-run --no-default-features --features "autoconvert usize isize bigint bigrational complex32 si std use_serde" diff --git a/Cargo.toml b/Cargo.toml index 66acf49c..87d425b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ typenum = "1.13" [dev-dependencies] approx = "0.5" +libm = "0.2" quickcheck = "1.0" serde_json = "1.0" static_assertions = "1.1" @@ -81,6 +82,8 @@ use_serde = ["serde"] rational-support = ["num-rational"] bigint-support = ["num-bigint", "num-rational/num-bigint-std"] complex-support = ["num-complex"] +# enable sin/cos for no_std builds, via libm +libm = [ "num-traits/libm" ] [[example]] name = "base" diff --git a/src/features.rs b/src/features.rs index ba4aa4f4..68a08b46 100644 --- a/src/features.rs +++ b/src/features.rs @@ -81,19 +81,19 @@ macro_rules! si { ($($tt:tt)*) => {}; } -/// Expands the given block of code when `uom` is compiled with the `std` feature. +/// Expands the given block of code when `uom` is compiled with either the `std` or the `libm` feature. #[doc(hidden)] #[macro_export] -#[cfg(feature = "std")] -macro_rules! std { +#[cfg(any(feature = "std", feature = "libm"))] +macro_rules! std_or_libm { ($($tt:tt)*) => { $($tt)* }; } -/// Does not expand the given block of code when `uom` is compiled without the `std` feature. +/// Does not expand the given block of code when `uom` is compiled without both the `std` and the `libm` feature. #[doc(hidden)] #[macro_export] -#[cfg(not(feature = "std"))] -macro_rules! std { +#[cfg(not(any(feature = "std", feature = "libm")))] +macro_rules! std_or_libm { ($($tt:tt)*) => {}; } diff --git a/src/lib.rs b/src/lib.rs index 498810d4..5c701af7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,7 @@ //! default. //! * `std` -- Feature to compile with standard library support. Disabling this feature compiles //! `uom` with `no_std`. Enabled by default. +//! * `libm` -- Enable advanced floatingpoint functions (sin, cos, ...) on no_std targets //! * `serde` -- Feature to enable support for serialization and deserialization of quantities with //! the [Serde][serde] crate. Disabled by default. //! @@ -268,9 +269,9 @@ pub mod lib { // Conditionally import num sub-crate types based on feature selection. #[doc(hidden)] pub mod num { - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] pub use num_traits::float::Float; - #[cfg(not(feature = "std"))] + #[cfg(not(any(feature = "std", feature = "libm")))] pub use num_traits::float::FloatCore as Float; pub use num_traits::{pow, FromPrimitive, Num, One, Saturating, Signed, ToPrimitive, Zero}; diff --git a/src/si/angle.rs b/src/si/angle.rs index 171a7c0d..adf150e2 100644 --- a/src/si/angle.rs +++ b/src/si/angle.rs @@ -1,6 +1,6 @@ //! Angle (dimensionless quantity). -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "libm"))] use super::ratio::Ratio; quantity! { @@ -64,7 +64,7 @@ impl Angle, f64> { } /// Implementation of various stdlib trigonometric functions -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "libm"))] impl Angle where U: crate::si::Units + ?Sized, @@ -121,7 +121,7 @@ where } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "libm"))] impl crate::si::Quantity where D: crate::si::Dimension + ?Sized, @@ -161,7 +161,7 @@ mod tests { } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] mod trig { storage_types! { types: Float; @@ -216,7 +216,12 @@ mod tests { quickcheck! { #[allow(trivial_casts)] fn atan2(y: V, x: V) -> bool { - Test::eq(&y.atan2(x), + let desired_value = if cfg!(feature = "libm"){ + libm::atan2f(y, x) + } else { + y.atan2(x) + }; + Test::eq(&desired_value, &Length::new::(y).atan2(Length::new::(x)).get::()) } } diff --git a/src/si/ratio.rs b/src/si/ratio.rs index 8683b393..3206dade 100644 --- a/src/si/ratio.rs +++ b/src/si/ratio.rs @@ -1,7 +1,7 @@ //! Ratio (dimensionless quantity). -#[cfg(feature = "std")] -use super::angle::{Angle, radian}; +#[cfg(any(feature = "std", feature = "libm"))] +use super::angle::{radian, Angle}; quantity! { /// Ratio (dimensionless quantity). @@ -34,7 +34,7 @@ quantity! { } /// Implementation of various stdlib functions. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", feature = "libm"))] impl Ratio where U: crate::si::Units + ?Sized, @@ -221,7 +221,7 @@ mod tests { } } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] mod float { storage_types! { types: Float; @@ -233,59 +233,129 @@ mod tests { quickcheck! { fn acos(x: V) -> bool { - Test::eq(&x.acos(), &Ratio::from(x).acos().get::()) + let desired_value = if cfg!(feature = "libm") { + libm::acosf(x) + } else { + x.acos() + }; + Test::eq(&desired_value, &Ratio::from(x).acos().get::()) } fn acosh(x: V) -> bool { - Test::eq(&x.acosh(), &Ratio::from(x).acosh().get::()) + let desired_value = if cfg!(feature = "libm") { + libm::acoshf(x) + } else { + x.acosh() + }; + Test::eq(&desired_value, &Ratio::from(x).acosh().get::()) } fn asin(x: V) -> bool { - Test::eq(&x.asin(), &Ratio::from(x).asin().get::()) + let desired_value = if cfg!(feature = "libm") { + libm::asinf(x) + } else { + x.asin() + }; + Test::eq(&desired_value, &Ratio::from(x).asin().get::()) } fn asinh(x: V) -> bool { - Test::eq(&x.asinh(), &Ratio::from(x).asinh().get::()) + let desired_value = if cfg!(feature = "libm") { + libm::asinhf(x) + } else { + x.asinh() + }; + Test::eq(&desired_value, &Ratio::from(x).asinh().get::()) } fn atan(x: V) -> bool { - Test::eq(&x.atan(), &Ratio::from(x).atan().get::()) + let desired_value = if cfg!(feature = "libm") { + libm::atanf(x) + } else { + x.atan() + }; + Test::eq(&desired_value, &Ratio::from(x).atan().get::()) } fn atanh(x: V) -> bool { - Test::eq(&x.atanh(), &Ratio::from(x).atanh().get::()) + let desired_value = if cfg!(feature = "libm") { + libm::atanhf(x) + } else { + x.atanh() + }; + Test::eq(&desired_value, &Ratio::from(x).atanh().get::()) } fn exp(x: V) -> bool { - Test::eq(&x.exp(), &Ratio::from(x).exp().get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::expf(x) + } else { + x.exp() + }; + Test::eq(&desired_value, &Ratio::from(x).exp().get::()) } fn exp2(x: V) -> bool { - Test::eq(&x.exp2(), &Ratio::from(x).exp2().get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::exp2f(x) + } else { + x.exp2() + }; + Test::eq(&desired_value, &Ratio::from(x).exp2().get::()) } fn ln(x: V) -> bool { - Test::eq(&x.ln(), &Ratio::from(x).ln().get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::logf(x) + } else { + x.ln() + }; + Test::eq(&desired_value, &Ratio::from(x).ln().get::()) } fn log(x: V, y: V) -> bool { - Test::eq(&x.log(y), &Ratio::from(x).log(y).get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::logf(x) / libm::logf(y) + } else { + x.log(y) + }; + Test::eq(&desired_value, &Ratio::from(x).log(y).get::()) } fn log2(x: V) -> bool { - Test::eq(&x.log2(), &Ratio::from(x).log2().get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::log2f(x) + } else { + x.log2() + }; + Test::eq(&desired_value, &Ratio::from(x).log2().get::()) } fn log10(x: V) -> bool { - Test::eq(&x.log10(), &Ratio::from(x).log10().get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::log10f(x) + } else { + x.log10() + }; + Test::eq(&desired_value, &Ratio::from(x).log10().get::()) } fn exp_m1(x: V) -> bool { - Test::eq(&x.exp_m1(), &Ratio::from(x).exp_m1().get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::expm1f(x) + } else { + x.exp_m1() + }; + Test::eq(&desired_value, &Ratio::from(x).exp_m1().get::()) } fn ln_1p(x: V) -> bool { - Test::eq(&x.ln_1p(), &Ratio::from(x).ln_1p().get::()) + let desired_value = if cfg!(feature = "libm"){ + libm::log1pf(x) + } else { + x.ln_1p() + }; + Test::eq(&desired_value, &Ratio::from(x).ln_1p().get::()) } } } diff --git a/src/system.rs b/src/system.rs index b90e3b25..11afe664 100644 --- a/src/system.rs +++ b/src/system.rs @@ -611,7 +611,7 @@ macro_rules! system { self.value.classify() } - std! { + std_or_libm! { autoconvert! { /// Calculates the length of the hypotenuse of a right-angle triangle given the legs. #[must_use = "method returns a new number and does not mutate the original value"] @@ -807,7 +807,7 @@ macro_rules! system { self.value.is_normal() } - std! { + std_or_libm! { /// Takes the cubic root of a number. /// #[cfg_attr(all(feature = "si", feature = "f32"), doc = " ```rust")] diff --git a/src/tests/quantity.rs b/src/tests/quantity.rs index 22068572..b2cdf9e7 100644 --- a/src/tests/quantity.rs +++ b/src/tests/quantity.rs @@ -468,16 +468,21 @@ mod float { Test::assert_eq(&3.3.fract(), &m1.fract::().get::()); } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] quickcheck! { #[allow(trivial_casts)] fn hypot_same(l: V, r: V) -> bool { - Test::eq(&l.hypot(r), + let desired_value = if cfg!(feature="libm") && ! cfg!(feature="std") { + libm::hypotf(l, r) + } else { + l.hypot(r) + }; + Test::eq(&desired_value, &f::Length::new::(l).hypot(f::Length::new::(r)).get::()) } } - #[cfg(all(feature = "std", feature = "autoconvert"))] + #[cfg(all(any(feature = "std", feature = "libm"), feature = "autoconvert"))] quickcheck! { #[allow(trivial_casts)] fn hypot_mixed(l: V, r: V) -> bool { diff --git a/src/tests/system.rs b/src/tests/system.rs index 99e48491..bd98a2ee 100644 --- a/src/tests/system.rs +++ b/src/tests/system.rs @@ -284,7 +284,7 @@ mod float { v.classify() == Length::new::(*v).classify() } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] #[allow(trivial_casts)] fn cbrt(v: A) -> bool { let l: Quantity, U, V> = Quantity::, U, V> { @@ -296,10 +296,15 @@ mod float { Test::eq(&v.cbrt(), &l.value) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] #[allow(trivial_casts)] fn hypot(l: A, r: A) -> bool { - Test::eq(&Length::new::(l.hypot(*r)), + let desired_value = if cfg!(feature="libm"){ + libm::hypotf(*l, *r) + } else { + l.hypot(*r) + }; + Test::eq(&Length::new::(desired_value), &Length::new::(*l).hypot(Length::new::(*r))) } @@ -313,7 +318,7 @@ mod float { v.is_sign_negative() == Length::new::(*v).is_sign_negative() } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] #[allow(trivial_casts)] fn mul_add(s: A, a: A, b: A) -> bool { let r: Quantity, U, V> = Length::new::(*s).mul_add( @@ -338,13 +343,13 @@ mod float { Test::eq(&v.recip(), &a.value) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] #[allow(trivial_casts)] fn powi(v: A) -> bool { Test::eq(&v.powi(3), &Length::new::(*v).powi(P3::new()).value) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] #[allow(trivial_casts)] fn sqrt(v: A) -> TestResult { if *v < V::zero() { @@ -645,7 +650,7 @@ mod complex { v.is_normal() == Length::new::(*v).is_normal() } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] #[allow(trivial_casts)] fn cbrt(v: A) -> bool { let l: Quantity, U, V> = Quantity::, U, V> { @@ -657,7 +662,7 @@ mod complex { Test::eq(&v.cbrt(), &l.value) } - #[cfg(feature = "std")] + #[cfg(any(feature = "std", feature = "libm"))] #[allow(trivial_casts)] fn mul_add(s: A, a: A, b: A) -> bool { #[allow(unused_imports)] From 843ea1b97f6c1eab82da885810c0f1bc9d593142 Mon Sep 17 00:00:00 2001 From: Moritz Meier Date: Tue, 10 Jan 2023 19:18:23 +0100 Subject: [PATCH 2/3] fix type errors due to libm crate not being generic --- Cargo.toml | 18 +++++++++--------- src/si/angle.rs | 2 +- src/si/ratio.rs | 28 ++++++++++++++-------------- src/tests/quantity.rs | 2 +- src/tests/system.rs | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87d425b6..25dd0de8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,23 +24,23 @@ features = ["usize", "u32", "u64", "isize", "i32", "i64", "bigint", "biguint", " maintenance = { status = "actively-developed" } [workspace] -members = [ - "tests/feature_check", - "uom-macros", - "tests/edition_check", -] +members = ["tests/feature_check", "uom-macros", "tests/edition_check"] [dependencies] num-traits = { version = "0.2", default-features = false } num-rational = { version = "0.4", optional = true, default-features = false } -num-bigint = { version = "0.4", optional = true, default-features = false, features = ["std"] } -num-complex = { version = "0.4", optional = true, default-features = false, features = ["std"] } +num-bigint = { version = "0.4", optional = true, default-features = false, features = [ + "std", +] } +num-complex = { version = "0.4", optional = true, default-features = false, features = [ + "std", +] } serde = { version = "1.0", optional = true, default-features = false } typenum = "1.13" [dev-dependencies] approx = "0.5" -libm = "0.2" +libm = { git = "https://github.com/moritz-meier/libm.git", branch = "feature/add-generic-helper" } quickcheck = "1.0" serde_json = "1.0" static_assertions = "1.1" @@ -83,7 +83,7 @@ rational-support = ["num-rational"] bigint-support = ["num-bigint", "num-rational/num-bigint-std"] complex-support = ["num-complex"] # enable sin/cos for no_std builds, via libm -libm = [ "num-traits/libm" ] +libm = ["num-traits/libm"] [[example]] name = "base" diff --git a/src/si/angle.rs b/src/si/angle.rs index adf150e2..ed337995 100644 --- a/src/si/angle.rs +++ b/src/si/angle.rs @@ -217,7 +217,7 @@ mod tests { #[allow(trivial_casts)] fn atan2(y: V, x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::atan2f(y, x) + libm::Libm::::atan2(y, x) } else { y.atan2(x) }; diff --git a/src/si/ratio.rs b/src/si/ratio.rs index 3206dade..c87eb424 100644 --- a/src/si/ratio.rs +++ b/src/si/ratio.rs @@ -234,7 +234,7 @@ mod tests { quickcheck! { fn acos(x: V) -> bool { let desired_value = if cfg!(feature = "libm") { - libm::acosf(x) + libm::Libm::::acos(x) } else { x.acos() }; @@ -243,7 +243,7 @@ mod tests { fn acosh(x: V) -> bool { let desired_value = if cfg!(feature = "libm") { - libm::acoshf(x) + libm::Libm::::acosh(x) } else { x.acosh() }; @@ -252,7 +252,7 @@ mod tests { fn asin(x: V) -> bool { let desired_value = if cfg!(feature = "libm") { - libm::asinf(x) + libm::Libm::::asin(x) } else { x.asin() }; @@ -261,7 +261,7 @@ mod tests { fn asinh(x: V) -> bool { let desired_value = if cfg!(feature = "libm") { - libm::asinhf(x) + libm::Libm::::asinh(x) } else { x.asinh() }; @@ -270,7 +270,7 @@ mod tests { fn atan(x: V) -> bool { let desired_value = if cfg!(feature = "libm") { - libm::atanf(x) + libm::Libm::::atan(x) } else { x.atan() }; @@ -279,7 +279,7 @@ mod tests { fn atanh(x: V) -> bool { let desired_value = if cfg!(feature = "libm") { - libm::atanhf(x) + libm::Libm::::atanh(x) } else { x.atanh() }; @@ -288,7 +288,7 @@ mod tests { fn exp(x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::expf(x) + libm::Libm::::exp(x) } else { x.exp() }; @@ -297,7 +297,7 @@ mod tests { fn exp2(x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::exp2f(x) + libm::Libm::::exp2(x) } else { x.exp2() }; @@ -306,7 +306,7 @@ mod tests { fn ln(x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::logf(x) + libm::Libm::::log(x) } else { x.ln() }; @@ -315,7 +315,7 @@ mod tests { fn log(x: V, y: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::logf(x) / libm::logf(y) + libm::Libm::::log(x) / libm::Libm::::log(y) } else { x.log(y) }; @@ -324,7 +324,7 @@ mod tests { fn log2(x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::log2f(x) + libm::Libm::::log2(x) } else { x.log2() }; @@ -333,7 +333,7 @@ mod tests { fn log10(x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::log10f(x) + libm::Libm::::log10(x) } else { x.log10() }; @@ -342,7 +342,7 @@ mod tests { fn exp_m1(x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::expm1f(x) + libm::Libm::::expm1(x) } else { x.exp_m1() }; @@ -351,7 +351,7 @@ mod tests { fn ln_1p(x: V) -> bool { let desired_value = if cfg!(feature = "libm"){ - libm::log1pf(x) + libm::Libm::::log1p(x) } else { x.ln_1p() }; diff --git a/src/tests/quantity.rs b/src/tests/quantity.rs index b2cdf9e7..27859c6a 100644 --- a/src/tests/quantity.rs +++ b/src/tests/quantity.rs @@ -473,7 +473,7 @@ mod float { #[allow(trivial_casts)] fn hypot_same(l: V, r: V) -> bool { let desired_value = if cfg!(feature="libm") && ! cfg!(feature="std") { - libm::hypotf(l, r) + libm::Libm::::hypot(l, r) } else { l.hypot(r) }; diff --git a/src/tests/system.rs b/src/tests/system.rs index bd98a2ee..e227cda5 100644 --- a/src/tests/system.rs +++ b/src/tests/system.rs @@ -300,7 +300,7 @@ mod float { #[allow(trivial_casts)] fn hypot(l: A, r: A) -> bool { let desired_value = if cfg!(feature="libm"){ - libm::hypotf(*l, *r) + libm::Libm::::hypot(*l, *r) } else { l.hypot(*r) }; From 14ba2c28dfb4201b5c04d20027e65e24687d6039 Mon Sep 17 00:00:00 2001 From: Wanja Zaeske Date: Mon, 17 Jul 2023 14:05:58 +0200 Subject: [PATCH 3/3] add missing qualifications for math ops --- src/system.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/system.rs b/src/system.rs index 11afe664..7ac1b33c 100644 --- a/src/system.rs +++ b/src/system.rs @@ -841,7 +841,7 @@ macro_rules! system { Quantity { dimension: $crate::lib::marker::PhantomData, units: $crate::lib::marker::PhantomData, - value: self.value.cbrt(), + value: $crate::num::Float::cbrt(self.value), } } @@ -907,7 +907,7 @@ macro_rules! system { Quantity { dimension: $crate::lib::marker::PhantomData, units: $crate::lib::marker::PhantomData, - value: self.value.powi(E::to_i32()), + value: $crate::num::Float::powi(self.value, E::to_i32()), } } @@ -945,7 +945,7 @@ macro_rules! system { Quantity { dimension: $crate::lib::marker::PhantomData, units: $crate::lib::marker::PhantomData, - value: self.value.sqrt(), + value: $crate::num::Float::sqrt(self.value), } }} }