From 7d9d06d92e09e82205ea9a2ffa894f77447c3673 Mon Sep 17 00:00:00 2001 From: Mike Boutin Date: Fri, 4 Jun 2021 07:25:58 -0400 Subject: [PATCH] [ci skip] FIXME --- Cargo.toml | 2 +- src/lib.rs | 2 + src/si/ratio.rs | 16 ++---- src/system.rs | 51 +++++++++++------ src/tests/mod.rs | 44 ++++++++++----- src/tests/quantity.rs | 124 ++++++++++++++++++++++++++++++++++-------- src/tests/system.rs | 2 +- 7 files changed, 177 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3f0cd416..3b383078 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ typenum = "1.13" [dev-dependencies] approx = "0.5" -quickcheck = "0.9.2" +quickcheck = "1.0" serde_json = "1.0" static_assertions = "1.1" diff --git a/src/lib.rs b/src/lib.rs index fb072b3a..b20b2a3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -441,11 +441,13 @@ pub trait Conversion { /// * `V`: Underlying storage type trait is implemented for. /// /// [factor]: https://jcgm.bipm.org/vim/en/1.24.html +#[allow(unused_qualifications)] // lib:cmp::PartialOrder false positive. pub trait ConversionFactor: lib::ops::Add + lib::ops::Sub + lib::ops::Mul + lib::ops::Div + + lib::cmp::PartialOrd + crate::num::Zero + crate::num::One { diff --git a/src/si/ratio.rs b/src/si/ratio.rs index 9c19b979..47abeba9 100644 --- a/src/si/ratio.rs +++ b/src/si/ratio.rs @@ -161,33 +161,29 @@ mod tests { use crate::si::quantities::*; use crate::tests::Test; - fn test_nan_or_eq(yl: V, yr: V) -> bool { - (yl.is_nan() && yr.is_nan()) || Test::eq(&yl, &yr) - } - quickcheck! { fn acos(x: V) -> bool { - test_nan_or_eq(x.acos(), Ratio::from(x).acos().get::()) + Test::eq(&x.acos(), &Ratio::from(x).acos().get::()) } fn acosh(x: V) -> bool { - test_nan_or_eq(x.acosh(), Ratio::from(x).acosh().get::()) + Test::eq(&x.acosh(), &Ratio::from(x).acosh().get::()) } fn asin(x: V) -> bool { - test_nan_or_eq(x.asin(), Ratio::from(x).asin().get::()) + Test::eq(&x.asin(), &Ratio::from(x).asin().get::()) } fn asinh(x: V) -> bool { - test_nan_or_eq(x.asinh(), Ratio::from(x).asinh().get::()) + Test::eq(&x.asinh(), &Ratio::from(x).asinh().get::()) } fn atan(x: V) -> bool { - test_nan_or_eq(x.atan(), Ratio::from(x).atan().get::()) + Test::eq(&x.atan(), &Ratio::from(x).atan().get::()) } fn atanh(x: V) -> bool { - test_nan_or_eq(x.atanh(), Ratio::from(x).atanh().get::()) + Test::eq(&x.atanh(), &Ratio::from(x).atanh().get::()) } } } diff --git a/src/system.rs b/src/system.rs index a16dc04d..ab67b7d8 100644 --- a/src/system.rs +++ b/src/system.rs @@ -303,16 +303,24 @@ macro_rules! system { where D: Dimension + ?Sized, U: Units + ?Sized, - V: $crate::Conversion + $crate::lib::ops::Mul, + V: $crate::Conversion, N: $crate::Conversion, { use $crate::typenum::Integer; - use $crate::Conversion; - use $crate::ConversionFactor; + use $crate::{Conversion, ConversionFactor}; + + let v = v.into_conversion(); + let n_coef = N::coefficient(); + let f = V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+; + let n_cons = N::constant($crate::ConstantOp::Sub); - (v.into_conversion() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+ - / N::coefficient() - N::constant($crate::ConstantOp::Sub)) - .value() + if n_coef < f { + (v * (f / n_coef) - n_cons).value() + } + else { + // (v * f / n_coef - n_cons).value() + (v / (n_coef / f) - n_cons).value() + } } /// Convert a value from the given unit to base units. @@ -327,16 +335,24 @@ macro_rules! system { where D: Dimension + ?Sized, U: Units + ?Sized, - V: $crate::Conversion + $crate::lib::ops::Mul, + V: $crate::Conversion, N: $crate::Conversion, { use $crate::typenum::Integer; - use $crate::Conversion; - use $crate::ConversionFactor; + use $crate::{Conversion, ConversionFactor}; - ((v.into_conversion() + N::constant($crate::ConstantOp::Add)) * N::coefficient() - / (V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+)) - .value() + let v = v.into_conversion(); + let n_coef = N::coefficient(); + let f = V::coefficient() $(* U::$name::coefficient().powi(D::$symbol::to_i32()))+; + let n_cons = N::constant($crate::ConstantOp::Add); + + if n_coef >= f { + ((v + n_cons) * (n_coef / f)).value() + } + else { + (((v + n_cons) * n_coef) / f).value() + // ((v + n_cons) / (f / n_coef)).value() + } } autoconvert_test! { @@ -357,12 +373,13 @@ macro_rules! system { V: $crate::Conversion + $crate::lib::ops::Mul, { use $crate::typenum::Integer; - use $crate::Conversion; - use $crate::ConversionFactor; + use $crate::{Conversion, ConversionFactor}; + + let v = v.into_conversion(); + let f = V::coefficient() $(* Ur::$name::coefficient().powi(D::$symbol::to_i32()) + / Ul::$name::coefficient().powi(D::$symbol::to_i32()))+; - (v.into_conversion() $(* Ur::$name::coefficient().powi(D::$symbol::to_i32()) - / Ul::$name::coefficient().powi(D::$symbol::to_i32()))+) - .value() + (v * f).value() }} #[doc(hidden)] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 7f733723..739c0408 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -108,12 +108,37 @@ mod test_trait { const ULPS: u32 = 3; impl super::super::Test for V { + /// Assert that `lhs` and `rhs` are exactly equal. + fn assert_eq(lhs: &Self, rhs: &Self) { + match (lhs.is_nan(), rhs.is_nan()) { + (true, true) => {} + _ => { assert_eq!(lhs, rhs); } + } + } + + /// Assert that `lhs` and `rhs` are approximately equal for floating point types or + /// exactly equal for other types. fn assert_approx_eq(lhs: &Self, rhs: &Self) { - assert_ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), max_ulps = ULPS); + match (lhs.is_nan(), rhs.is_nan()) { + (true, true) => {} + _ => { + assert_ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), + max_ulps = ULPS); + } + } + } + + /// Exactly compare `lhs` and `rhs` and return the result. + fn eq(lhs: &Self, rhs: &Self) -> bool { + (lhs.is_nan() && rhs.is_nan()) + || lhs == rhs } + /// Approximately compare `lhs` and `rhs` for floating point types or exactly compare + /// for other types and return the result. fn approx_eq(lhs: &Self, rhs: &Self) -> bool { - ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), max_ulps = ULPS) + (lhs.is_nan() && rhs.is_nan()) + || ulps_eq!(lhs, rhs, epsilon = EPS_FACTOR * V::epsilon(), max_ulps = ULPS) } } } @@ -140,32 +165,25 @@ impl crate::lib::ops::Deref for A { mod a_struct { storage_types! { - // Quickcheck 0.8 required for i128/u128 support. Use PrimInt after upgrade. - types: Float, usize, u8, u16, u32, u64, isize, i8, i16, i32, i64; + types: Float, PrimInt; use super::super::A; impl quickcheck::Arbitrary for A { - fn arbitrary(g: &mut G) -> Self - where - G: quickcheck::Gen, - { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { A { v: V::arbitrary(g), } } } } storage_types! { - types: BigInt, BigUint, Ratio, i128, u128; + types: BigInt, BigUint, Ratio; use crate::num::FromPrimitive; use super::super::A; impl quickcheck::Arbitrary for A { - fn arbitrary(g: &mut G) -> Self - where - G: quickcheck::Gen, - { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { A { v: loop { let v = V::from_f64(::arbitrary(g)); diff --git a/src/tests/quantity.rs b/src/tests/quantity.rs index 7ccc6565..52249ec3 100644 --- a/src/tests/quantity.rs +++ b/src/tests/quantity.rs @@ -6,6 +6,13 @@ storage_types! { mod f { Q!(crate::tests, super::V); } mod k { Q!(crate::tests, super::V, (kilometer, kilogram, kelvin)); } + // Used to manually calculate results in tests with the underlying storage types. Many + // quickcheck inputs have numerical precision issues and tests need to be discarded. + #[cfg(feature = "autoconvert")] + fn kilo() -> V { + V::from_f64(1000.0).unwrap() + } + #[test] fn new() { let l1 = k::Length::new::(V::one()); @@ -23,6 +30,13 @@ storage_types! { let l2 = k::Length::new::(V::one()); let m1 = k::Mass::new::(V::one()); + type KilometerFahrenheitBase = dyn Units; + + let a1 = to_base::(&V::one()); + let a2 = from_base::(&a1); + + println!("{:?}, {:?}, {:?}, {:?}", a1, a2, l1, l1.get::()); + Test::assert_eq(&V::from_f64(1000.0).unwrap(), &l1.get::()); Test::assert_eq(&V::one(), &l2.get::()); Test::assert_eq(&V::one(), &l1.get::()); @@ -54,30 +68,58 @@ storage_types! { #[cfg(feature = "autoconvert")] quickcheck! { #[allow(trivial_casts)] - fn add(l: A, r: A) -> bool { - Test::approx_eq(&k::Length::new::(&*l + &*r), - &(k::Length::new::((*l).clone()) - + f::Length::new::((*r).clone()))) + fn add(l: A, r: A) -> TestResult { + let f = (&*l + &*r) / kilo(); + let i = (&*l / kilo()) + (&*r / kilo()); + + if i != f { + return TestResult::discard(); + } + + TestResult::from_bool( + Test::approx_eq(&k::Length::new::(&*l + &*r), + &(k::Length::new::((*l).clone()) + + f::Length::new::((*r).clone())))) } #[allow(trivial_casts)] - fn sub(l: A, r: A) -> bool { - Test::approx_eq(&k::Length::new::(&*l - &*r), - &(k::Length::new::((*l).clone()) - - f::Length::new::((*r).clone()))) + fn sub(l: A, r: A) -> TestResult { + let f = (&*l - &*r) / kilo(); + let i = (&*l / kilo()) - (&*r / kilo()); + + if i != f { + return TestResult::discard(); + } + + TestResult::from_bool( + Test::approx_eq(&k::Length::new::(&*l - &*r), + &(k::Length::new::((*l).clone()) + - f::Length::new::((*r).clone())))) } #[allow(trivial_casts)] - fn mul_quantity(l: A, r: A) -> bool { - Test::approx_eq(&/*Area::new::*/(&*l * &*r), - &(f::Length::new::((*l).clone()) - * k::Length::new::((*r).clone())).value) - && Test::approx_eq(&/*Area::new::*/(&*l * &*r), - &(f::Length::new::((*l).clone()) - * k::Mass::new::((*r).clone())).value) - && Test::approx_eq(&/*Area::new::*/(&*l * &*r), - &(k::Length::new::((*l).clone()) - * f::Mass::new::((*r).clone())).value) + fn mul_quantity(l: A, r: A) -> TestResult { + let f = &*l * &*r; + let i1 = (&*l * &(&*r / kilo())) * kilo(); + let i2 = &*l * &*r; + let i3 = &(&*l * kilo()) / kilo() * &*r; + + if i1 != f + || i2 != f + || i3 != f { + return TestResult::discard(); + } + + TestResult::from_bool( + Test::approx_eq(&/*Area::new::*/(&*l * &*r), + &(f::Length::new::((*l).clone()) + * k::Length::new::((*r).clone())).value) + && Test::approx_eq(&/*Length-mass*/(&*l * &*r), + &(f::Length::new::((*l).clone()) + * k::Mass::new::((*r).clone())).value) + && Test::approx_eq(&/*Length-mass*/(&*l * &*r), + &(k::Length::new::((*l).clone()) + * f::Mass::new::((*r).clone())).value)) } #[allow(trivial_casts)] @@ -98,6 +140,19 @@ storage_types! { if *r == V::zero() { return TestResult::discard(); } + + let f = (&*l % &*r) / kilo(); + let i = (&*l / kilo()) % (&*r / kilo()); + + if i != f { + return TestResult::discard(); + } + + println!("{:?} = {:?}", f, i); + println!("{:?} = {:?}", + k::Length::new::(&*l % &*r), + k::Length::new::((*l).clone()) + % f::Length::new::((*r).clone())); TestResult::from_bool( Test::approx_eq(&k::Length::new::(&*l % &*r), @@ -300,27 +355,46 @@ mod non_big { mod f { Q!(crate::tests, super::V); } mod k { Q!(crate::tests, super::V, (kilometer, kilogram, kelvin)); } + // Used to manually calculate results in tests with the underlying storage types. Many + // quickcheck inputs have numerical precision issues and tests need to be discarded. + #[cfg(feature = "autoconvert")] + fn kilo() -> V { + V::from_f64(1000.0).unwrap() + } + quickcheck! { #[allow(trivial_casts)] - fn add_assign(l: A, r: A) -> bool { + fn add_assign(l: A, r: A) -> TestResult { let mut f = *l; + let mut i = *l / kilo(); let mut v = k::Length::new::(*l); f += *r; + i += *r / kilo(); v += f::Length::new::(*r); - Test::approx_eq(&k::Length::new::(f), &v) + if i != f / kilo() { + return TestResult::discard(); + } + + TestResult::from_bool(Test::approx_eq(&k::Length::new::(f), &v)) } #[allow(trivial_casts)] - fn sub_assign(l: A, r: A) -> bool { + fn sub_assign(l: A, r: A) -> TestResult { let mut f = *l; + let mut i = *l / kilo(); let mut v = k::Length::new::(*l); f -= *r; + i -= *r / kilo(); v -= f::Length::new::(*r); - Test::approx_eq(&k::Length::new::(f), &v) + if i != f / kilo() { + return TestResult::discard(); + } + + TestResult::from_bool(Test::approx_eq(&k::Length::new::(f), &v)) } #[allow(trivial_casts)] @@ -330,11 +404,17 @@ mod non_big { } let mut f = *l; + let mut i = *l / kilo(); let mut v = k::Length::new::(*l); f %= *r; + i %= *r / kilo(); v %= f::Length::new::(*r); + if !Test::eq(&i, &(f / kilo())) { + return TestResult::discard(); + } + TestResult::from_bool(Test::approx_eq(&k::Length::new::(f), &v)) } } diff --git a/src/tests/system.rs b/src/tests/system.rs index 87d366b6..68864cd0 100644 --- a/src/tests/system.rs +++ b/src/tests/system.rs @@ -52,7 +52,7 @@ storage_types! { // &crate::tests::from_base::( // &*v)) // kelvin -> fahrenheit. - && Test::approx_eq(&(&*v / &f_coefficient - &f_constant), + && Test::approx_eq(&((&*v * (&V::one() / &f_coefficient)) - &f_constant), &crate::tests::from_base::( &*v)) // fahrenheit -> kelvin.