Skip to content

Commit

Permalink
Check unit validity, expose validity to end-users.
Browse files Browse the repository at this point in the history
Previous explicit `TypeId` checks are replaced with
`Conversion::is_valid` calls in tests to ensure a unit is valid for the
underlying storage type.
  • Loading branch information
iliekturtles committed Aug 29, 2024
1 parent cb2dadf commit 33013cf
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 53 deletions.
2 changes: 2 additions & 0 deletions src/quantity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ macro_rules! quantity {
where
V: $crate::Conversion<V>,
{
#[cfg(test)]
fn is_valid() -> bool;
}

unit! {
Expand Down
8 changes: 5 additions & 3 deletions src/si/acceleration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,11 @@ mod tests {
test::<l::millimeter, t::minute, a::millimeter_per_minute_squared>();

fn test<L: l::Conversion<V>, T: t::Conversion<V>, A: a::Conversion<V>>() {
Test::assert_eq(&Acceleration::new::<A>(V::one()),
&(Length::new::<L>(V::one()) /
(Time::new::<T>(V::one()) * Time::new::<T>(V::one()))));
if A::is_valid() {
Test::assert_eq(&Acceleration::new::<A>(V::one()),
&(Length::new::<L>(V::one()) /
(Time::new::<T>(V::one()) * Time::new::<T>(V::one()))));
}
}
}
}
Expand Down
22 changes: 7 additions & 15 deletions src/si/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ quantity! {
#[cfg(test)]
mod tests {
storage_types! {
use crate::lib::any::TypeId;
use crate::num::One;
use crate::si::area as a;
use crate::si::length as l;
Expand All @@ -87,12 +86,8 @@ mod tests {
// #392: Disable tests on ARM until issues with floating point behavior can be resolved.
#[cfg(not(target_arch = "arm"))]
fn check_units() {
// Values too large for f32.
if TypeId::of::<f64>() == TypeId::of::<V>() {
test::<l::yottameter, a::square_yottameter>();
test::<l::zettameter, a::square_zettameter>();
}

test::<l::yottameter, a::square_yottameter>();
test::<l::zettameter, a::square_zettameter>();
test::<l::exameter, a::square_exameter>();
test::<l::petameter, a::square_petameter>();
test::<l::terameter, a::square_terameter>();
Expand All @@ -111,16 +106,13 @@ mod tests {
test::<l::femtometer, a::square_femtometer>();
test::<l::attometer, a::square_attometer>();
test::<l::zeptometer, a::square_zeptometer>();

// Values too small for f32.
if TypeId::of::<f64>() == TypeId::of::<V>() {
test::<l::yoctometer, a::square_yoctometer>();
}
test::<l::yoctometer, a::square_yoctometer>();

fn test<L: l::Conversion<V>, A: a::Conversion<V>>() {
Test::assert_eq(&Area::new::<A>(V::one()),
&(Length::new::<L>(V::one()) * Length::new::<L>(V::one())));
}
if A::is_valid() {
Test::assert_eq(&Area::new::<A>(V::one()),
&(Length::new::<L>(V::one()) * Length::new::<L>(V::one())));
} }
}
}
}
10 changes: 6 additions & 4 deletions src/si/force.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,12 @@ mod tests {
T: t::Conversion<V>,
F: f::Conversion<V>>()
{
Test::assert_approx_eq(&Force::new::<F>(V::one()),
&((Mass::new::<M>(V::one())
* Length::new::<L>(V::one()))
/ (Time::new::<T>(V::one()) * Time::new::<T>(V::one()))));
if F::is_valid() && T::is_valid() {
Test::assert_approx_eq(&Force::new::<F>(V::one()),
&((Mass::new::<M>(V::one())
* Length::new::<L>(V::one()))
/ (Time::new::<T>(V::one()) * Time::new::<T>(V::one()))));
}
}
}
}
Expand Down
14 changes: 9 additions & 5 deletions src/si/frequency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,15 @@ mod tests {
test::<t::year, f::cycle_per_year>();

fn test<T: t::Conversion<V>, F: f::Conversion<V>>() {
Test::assert_approx_eq(&(V::one() / Time::new::<T>(V::one())),
&Frequency::new::<F>(V::one()));
Test::assert_approx_eq(&Time::new::<T>(V::one()),
&(V::one() / Frequency::new::<F>(V::one())));
}
if T::is_valid() {
Test::assert_approx_eq(&(V::one() / Time::new::<T>(V::one())),
&Frequency::new::<F>(V::one()));
}

if F::is_valid() {
Test::assert_approx_eq(&Time::new::<T>(V::one()),
&(V::one() / Frequency::new::<F>(V::one())));
} }
}
}
}
6 changes: 4 additions & 2 deletions src/si/velocity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ mod test {
test::<l::millimeter, t::minute, v::millimeter_per_minute>();

fn test<L: l::Conversion<V>, T: t::Conversion<V>, E: v::Conversion<V>>() {
Test::assert_eq(&Velocity::new::<E>(V::one()),
&(Length::new::<L>(V::one()) / Time::new::<T>(V::one())));
if E::is_valid() {
Test::assert_eq(&Velocity::new::<E>(V::one()),
&(Length::new::<L>(V::one()) / Time::new::<T>(V::one())));
}
}
}
}
Expand Down
33 changes: 13 additions & 20 deletions src/si/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ quantity! {
#[cfg(test)]
mod tests {
storage_types! {
use crate::lib::any::TypeId;
use crate::num::{FromPrimitive, One};
use crate::si::area as a;
use crate::si::length as l;
Expand Down Expand Up @@ -160,14 +159,10 @@ mod tests {
// #392: Disable tests on ARM until issues with floating point behavior can be resolved.
#[cfg(not(target_arch = "arm"))]
fn check_units() {
// Values too large for f32.
if TypeId::of::<f64>() == TypeId::of::<V>() {
test::<l::yottameter, v::cubic_yottameter>();
test::<l::zettameter, v::cubic_zettameter>();
test::<l::exameter, v::cubic_exameter>();
test::<l::petameter, v::cubic_petameter>();
}

test::<l::yottameter, v::cubic_yottameter>();
test::<l::zettameter, v::cubic_zettameter>();
test::<l::exameter, v::cubic_exameter>();
test::<l::petameter, v::cubic_petameter>();
test::<l::terameter, v::cubic_terameter>();
test::<l::gigameter, v::cubic_gigameter>();
test::<l::megameter, v::cubic_megameter>();
Expand All @@ -182,19 +177,17 @@ mod tests {
test::<l::nanometer, v::cubic_nanometer>();
test::<l::picometer, v::cubic_picometer>();
test::<l::femtometer, v::cubic_femtometer>();

// Values too small for f32.
if TypeId::of::<f64>() == TypeId::of::<V>() {
test::<l::attometer, v::cubic_attometer>();
test::<l::zeptometer, v::cubic_zeptometer>();
test::<l::yoctometer, v::cubic_yoctometer>();
}
test::<l::attometer, v::cubic_attometer>();
test::<l::zeptometer, v::cubic_zeptometer>();
test::<l::yoctometer, v::cubic_yoctometer>();

fn test<L: l::Conversion<V>, O: v::Conversion<V>>() {
Test::assert_eq(&Volume::new::<O>(V::one()),
&(Length::new::<L>(V::one())
* Length::new::<L>(V::one())
* Length::new::<L>(V::one())));
if O::is_valid() {
Test::assert_eq(&Volume::new::<O>(V::one()),
&(Length::new::<L>(V::one())
* Length::new::<L>(V::one())
* Length::new::<L>(V::one())));
}
}
}
}
Expand Down
90 changes: 86 additions & 4 deletions src/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,18 @@ macro_rules! unit {
}
}

impl super::Conversion<V> for super::$unit {})+
impl super::Conversion<V> for super::$unit {
#[cfg(test)]
#[inline(always)]
fn is_valid() -> bool {
use $crate::num::ToPrimitive;

let r = Some(unit!(@coefficient $($conversion),+));
let c = <Self as $crate::Conversion<V>>::coefficient().to_f64();

r == c
}
})+
}

storage_types! {
Expand All @@ -187,7 +198,31 @@ macro_rules! unit {
}
}

impl super::Conversion<V> for super::$unit {})+
impl super::Conversion<V> for super::$unit {
#[cfg(test)]
#[inline(always)]
fn is_valid() -> bool {
use $crate::num::{FromPrimitive, ToPrimitive};

if let Some(conversion) = Self::T::from_f64(unit!(@coefficient $($conversion),+)) {
// Fractional conversion factors will end up being truncated.
if conversion.numer() >= conversion.denom() {
if let Some(numer) = conversion.numer().to_f64() {
if let Some(denom) = conversion.denom().to_f64() {
// Wrap expression in {}s to avoid error:
// error[E0658]: attributes on expressions are experimental
let r = { unit!(@coefficient $($conversion),+) };
let c = numer / denom;

return r == c
}
}
}
}

false
}
})+
}

storage_types! {
Expand Down Expand Up @@ -219,7 +254,30 @@ macro_rules! unit {
}
}

impl super::Conversion<V> for super::$unit {})+
impl super::Conversion<V> for super::$unit {
#[cfg(test)]
#[inline(always)]
fn is_valid() -> bool {
use $crate::num::{FromPrimitive, ToPrimitive};

if let Some(conversion) = $crate::num::rational::Ratio::<$crate::num::BigInt>::from_f64(unit!(@coefficient $($conversion),+)) {
if conversion.numer() >= conversion.denom() {
if let Some(numer) = conversion.numer().to_f64() {
if let Some(denom) = conversion.denom().to_f64() {
// Wrap expression in {}s to avoid error:
// error[E0658]: attributes on expressions are experimental
let r = { unit!(@coefficient $($conversion),+) };
let c = numer / denom;

return r == c
}
}
}
}

false
}
})+
}

storage_types! {
Expand All @@ -245,7 +303,31 @@ macro_rules! unit {
}
}

impl super::Conversion<V> for super::$unit {})+
impl super::Conversion<V> for super::$unit {
#[cfg(test)]
#[inline(always)]
fn is_valid() -> bool {
use $crate::num::{FromPrimitive, ToPrimitive};

if let Some(conversion) = Self::T::from_f64(unit!(@coefficient $($conversion),+)) {
// Factional conversion factors will end up being truncated.
if conversion.numer() >= conversion.denom() {
if let Some(numer) = conversion.numer().to_f64() {
if let Some(denom) = conversion.denom().to_f64() {
// Wrap expression in {}s to avoid error:
// error[E0658]: attributes on expressions are experimental
let r = { unit!(@coefficient $($conversion),+) };
let c = numer / denom;

return r == c
}
}
}
}

false
}
})+
}

storage_types! {
Expand Down

0 comments on commit 33013cf

Please sign in to comment.