diff --git a/src/lib.rs b/src/lib.rs index 006978af..bee9bf0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -346,6 +346,32 @@ pub mod si; #[cfg(test)] mod tests; +/// Operations performed on the constant portion of the [conversion factor][factor]. Used to help +/// guide optimizations when floating point underlying storage types are used. +/// +/// For a value, `v: Float`, adding `-0.0` is a no-op while adding `0.0` will change the sign if +/// `v` is `-0.0`. The opposite is true for subtraction. +/// +/// ```ignore +/// v +/// 0.0 + -0.0 = 0.0 +/// -0.0 + 0.0 = 0.0 // v + 0.0 != v +/// -0.0 + -0.0 = -0.0 +/// 0.0 - -0.0 = 0.0 +/// -0.0 - 0.0 = 0.0 +/// -0.0 - -0.0 = 0.0 // v - -0.0 != v +/// ``` +/// +/// [factor]: https://jcgm.bipm.org/vim/en/1.24.html +#[derive(Clone, Copy, Debug)] +pub enum ConstantOp { + /// Hint that the constant is being added to a value. + Add, + + /// Hint that the constant is being subtracted from a value. + Sub, +} + /// Trait to identify [units][units] which have a [conversion factor][factor]. /// /// [units]: http://jcgm.bipm.org/vim/en/1.13.html @@ -355,9 +381,8 @@ pub trait Conversion { type T: ConversionFactor; /// Coefficient portion of [conversion factor][factor] for converting the given unit to the - /// base unit for the quantity: `(value * coefficient()) + constant()`. - /// - /// Default implementation returns `Self::T::one()`. + /// base unit for the quantity: `(value * coefficient()) + constant()`. Implementation should + /// return the multiplicative identity (`Self::T::one()`) if no coefficient exists. /// /// [factor]: https://jcgm.bipm.org/vim/en/1.24.html #[inline(always)] @@ -366,13 +391,15 @@ pub trait Conversion { } /// Constant portion of [conversion factor][factor] for converting the given unit to the base - /// unit for the quantity: `(value * coefficient()) + constant()`. - /// - /// Default implementation returns `Self::T::zero()`. + /// unit for the quantity: `(value * coefficient()) + constant()`. Implementation should return + /// the additive identity (`Self::T::zero()`) if no constant exists. See + /// [ConstantOp](enum.ConstantOp.html) documentation for details about parameter use to use to + /// ensure method optimizes correctly. /// /// [factor]: https://jcgm.bipm.org/vim/en/1.24.html #[inline(always)] - fn constant() -> Self::T { + #[allow(unused_variables)] + fn constant(op: ConstantOp) -> Self::T { ::zero() } @@ -434,6 +461,14 @@ storage_types! { impl ::Conversion for V { type T = V; + #[inline(always)] + fn constant(op: ::ConstantOp) -> Self::T { + match op { + ::ConstantOp::Add => -::zero(), + ::ConstantOp::Sub => ::zero(), + } + } + #[inline(always)] fn into_conversion(&self) -> Self::T { *self diff --git a/src/quantity.rs b/src/quantity.rs index b0ca6b27..54e914ec 100644 --- a/src/quantity.rs +++ b/src/quantity.rs @@ -176,8 +176,9 @@ macro_rules! quantity { } #[inline(always)] - fn constant() -> Self::T { - quantity!(@constant $($conversion),+) + #[allow(unused_variables)] + fn constant(op: $crate::ConstantOp) -> Self::T { + quantity!(@constant op $($conversion),+) } } @@ -202,8 +203,9 @@ macro_rules! quantity { } #[inline(always)] - fn constant() -> Self::T { - from_f64(quantity!(@constant $($conversion),+)) + #[allow(unused_variables)] + fn constant(op: $crate::ConstantOp) -> Self::T { + from_f64(quantity!(@constant op $($conversion),+)) } } @@ -233,8 +235,9 @@ macro_rules! quantity { } #[inline(always)] - fn constant() -> Self::T { - from_f64(quantity!(@constant $($conversion),+)) + #[allow(unused_variables)] + fn constant(op: $crate::ConstantOp) -> Self::T { + from_f64(quantity!(@constant op $($conversion),+)) } } @@ -258,8 +261,9 @@ macro_rules! quantity { } #[inline(always)] - fn constant() -> Self::T { - from_f64(quantity!(@constant $($conversion),+)) + #[allow(unused_variables)] + fn constant(op: $crate::ConstantOp) -> Self::T { + from_f64(quantity!(@constant op $($conversion),+)) } } @@ -492,6 +496,11 @@ macro_rules! quantity { }; (@coefficient $factor:expr, $const:expr) => { $factor }; (@coefficient $factor:expr) => { $factor }; - (@constant $factor:expr, $const:expr) => { $const }; - (@constant $factor:expr) => { 0.0 }; + (@constant $op:ident $factor:expr, $const:expr) => { $const }; + (@constant $op:ident $factor:expr) => { + match $op { + $crate::ConstantOp::Add => -0.0, + $crate::ConstantOp::Sub => 0.0, + } + }; } diff --git a/src/system.rs b/src/system.rs index aee5c80f..05be29e6 100644 --- a/src/system.rs +++ b/src/system.rs @@ -282,7 +282,7 @@ macro_rules! system { use $crate::ConversionFactor; (v.into_conversion() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+ - / N::coefficient() - N::constant()) + / N::coefficient() - N::constant($crate::ConstantOp::Sub)) .value() } @@ -299,7 +299,7 @@ macro_rules! system { use $crate::Conversion; use $crate::ConversionFactor; - ((v.into_conversion() + N::constant()) * N::coefficient() + ((v.into_conversion() + N::constant($crate::ConstantOp::Add)) * N::coefficient() / (V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+)) .value() } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index b1f17a49..12b2b393 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -16,7 +16,7 @@ use str::ParseQuantityError; #[allow(unused_imports)] use typenum::{N1, P1, P2, P3, Z0}; #[allow(unused_imports)] -use {Conversion, ConversionFactor}; +use {ConstantOp, Conversion, ConversionFactor}; #[macro_use] mod length { diff --git a/src/tests/quantities.rs b/src/tests/quantities.rs index a5f30f92..aa156250 100644 --- a/src/tests/quantities.rs +++ b/src/tests/quantities.rs @@ -86,9 +86,10 @@ storage_types! { #[test] fn constant() { - Test::assert_eq(&V::zero(), &>::constant().value()); + Test::assert_eq(&V::zero(), + &>::constant(ConstantOp::Add).value()); Test::assert_eq(&V::from_f64(459.67).unwrap(), - &>::constant().value()); + &>::constant(ConstantOp::Add).value()); } #[cfg(feature = "std")] diff --git a/src/tests/system.rs b/src/tests/system.rs index 6d3d3180..f78f19f4 100644 --- a/src/tests/system.rs +++ b/src/tests/system.rs @@ -24,7 +24,8 @@ storage_types! { { let km: V = >::coefficient().value(); let f_coefficient: V = >::coefficient().value(); - let f_constant: V = >::constant().value(); + let f_constant: V = + >::constant(ConstantOp::Add).value(); // meter -> meter. Test::approx_eq(&*v, @@ -63,7 +64,8 @@ storage_types! { { let km: V = >::coefficient().value(); let f_coefficient: V = >::coefficient().value(); - let f_constant: V = >::constant().value(); + let f_constant: V = + >::constant(ConstantOp::Add).value(); // meter -> meter. Test::approx_eq(&*v,