Skip to content

Commit

Permalink
Correct to_base and from_base to be zero-cost for float storage t…
Browse files Browse the repository at this point in the history
…ypes.

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.

When no constant factor exists use an appropriated signed `0.0`. A new
enum, `ConstantOp`, is added to allow the `constant(op: ConstantOp)`
function to return an appropriately signed `0.0`.

   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
  • Loading branch information
iliekturtles committed Jun 17, 2019
1 parent 1c7dc72 commit 9f5ad77
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 24 deletions.
49 changes: 42 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -355,9 +381,8 @@ pub trait Conversion<V> {
type T: ConversionFactor<V>;

/// 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)]
Expand All @@ -366,13 +391,15 @@ pub trait Conversion<V> {
}

/// 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 {
<Self::T as num::Zero>::zero()
}

Expand Down Expand Up @@ -434,6 +461,14 @@ storage_types! {
impl ::Conversion<V> for V {
type T = V;

#[inline(always)]
fn constant(op: ::ConstantOp) -> Self::T {
match op {
::ConstantOp::Add => -<Self::T as ::num::Zero>::zero(),
::ConstantOp::Sub => <Self::T as ::num::Zero>::zero(),
}
}

#[inline(always)]
fn into_conversion(&self) -> Self::T {
*self
Expand Down
29 changes: 19 additions & 10 deletions src/quantity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),+)
}
}

Expand All @@ -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),+))
}
}

Expand Down Expand Up @@ -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),+))
}
}

Expand All @@ -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),+))
}
}

Expand Down Expand Up @@ -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,
}
};
}
4 changes: 2 additions & 2 deletions src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand All @@ -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()
}
Expand Down
2 changes: 1 addition & 1 deletion src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions src/tests/quantities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ storage_types! {

#[test]
fn constant() {
Test::assert_eq(&V::zero(), &<kilogram as Conversion<V>>::constant().value());
Test::assert_eq(&V::zero(),
&<kilogram as Conversion<V>>::constant(ConstantOp::Add).value());
Test::assert_eq(&V::from_f64(459.67).unwrap(),
&<degree_fahrenheit as Conversion<V>>::constant().value());
&<degree_fahrenheit as Conversion<V>>::constant(ConstantOp::Add).value());
}

#[cfg(feature = "std")]
Expand Down
6 changes: 4 additions & 2 deletions src/tests/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ storage_types! {
{
let km: V = <kilometer as ::Conversion<V>>::coefficient().value();
let f_coefficient: V = <degree_fahrenheit as ::Conversion<V>>::coefficient().value();
let f_constant: V = <degree_fahrenheit as ::Conversion<V>>::constant().value();
let f_constant: V =
<degree_fahrenheit as ::Conversion<V>>::constant(ConstantOp::Add).value();

// meter -> meter.
Test::approx_eq(&*v,
Expand Down Expand Up @@ -63,7 +64,8 @@ storage_types! {
{
let km: V = <kilometer as ::Conversion<V>>::coefficient().value();
let f_coefficient: V = <degree_fahrenheit as ::Conversion<V>>::coefficient().value();
let f_constant: V = <degree_fahrenheit as ::Conversion<V>>::constant().value();
let f_constant: V =
<degree_fahrenheit as ::Conversion<V>>::constant(ConstantOp::Add).value();

// meter -> meter.
Test::approx_eq(&*v,
Expand Down

0 comments on commit 9f5ad77

Please sign in to comment.