diff --git a/README.md b/README.md index 1657db6..94faef0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![crates.io badge](https://img.shields.io/crates/v/appro-eq.svg)](https://crates.io/crates/appro-eq) [![Build Status](https://travis-ci.org/chalharu/rust-appro-eq.svg?branch=master)](https://travis-ci.org/chalharu/rust-appro-eq) -[![docs.rs](https://docs.rs/appro-eq/badge.svg)](https://docs.rs/appro_eq) +[![docs.rs](https://docs.rs/appro-eq/badge.svg)](https://docs.rs/appro-eq) [![Coverage Status](https://coveralls.io/repos/github/chalharu/rust-appro-eq/badge.svg?branch=master)](https://coveralls.io/github/chalharu/rust-appro-eq?branch=master) Implementing the `ApproEq` traits, Can asserts that the two expressions are approximately equal to each other. diff --git a/src/lib.rs b/src/lib.rs index 2db29ec..bffe357 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ mod ndarray_impl; use std::rc::{Rc, Weak}; use std::sync::Arc; use std::cell::{Cell, RefCell}; +use std::time::{Duration, Instant, SystemTime}; use std::error; use std::fmt; @@ -77,16 +78,19 @@ pub enum ApproEqError { NonNumDifference, #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] DividedByZero, + #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] + Overflow, + #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] + ComponentError( + #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] + Box + ), } #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] impl fmt::Display for ApproEqError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &ApproEqError::LengthMismatch => write!(f, "length mismatch"), - &ApproEqError::NonNumDifference => write!(f, "non num difference"), - &ApproEqError::DividedByZero => write!(f, "divided by zero"), - } + write!(f, "{}", error::Error::description(self)) } } @@ -97,15 +101,13 @@ impl error::Error for ApproEqError { &ApproEqError::LengthMismatch => "length mismatch", &ApproEqError::NonNumDifference => "non num difference", &ApproEqError::DividedByZero => "divided by zero", + &ApproEqError::Overflow => "overflow", + &ApproEqError::ComponentError(ref err) => err.description() } } fn cause(&self) -> Option<&error::Error> { - match self { - &ApproEqError::LengthMismatch => None, - &ApproEqError::NonNumDifference => None, - &ApproEqError::DividedByZero => None, - } + None } } @@ -123,12 +125,38 @@ pub trait AbsError { fn abs_error(&self, expected: &Rhs) -> ApproEqResult; } +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +pub trait AbsTolerance { + #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] + fn abs_tolerance() -> Diff; +} + +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +pub trait RelTolerance { + #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] + fn rel_tolerance() -> Diff; +} + #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] pub trait Tolerance { #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] fn tolerance() -> Diff; } +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl> AbsTolerance for Diff { + fn abs_tolerance() -> Diff { + Diff::tolerance() + } +} + +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl> RelTolerance for Diff { + fn rel_tolerance() -> Diff { + Diff::tolerance() + } +} + /// Trait for approximately equality comparisons. #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] pub trait AbsApproEqWithTol { @@ -177,10 +205,10 @@ pub trait AbsApproEq { } #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] -impl, T: AbsApproEqWithTol> AbsApproEq +impl, T: AbsApproEqWithTol> AbsApproEq for T { fn abs_appro_eq(&self, other: &Rhs) -> bool { - self.abs_appro_eq_with_tol(other, &Diff::tolerance()) + self.abs_appro_eq_with_tol(other, &Diff::abs_tolerance()) } } @@ -232,13 +260,14 @@ pub trait RelApproEq { } #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] -impl, T: RelApproEqWithTol> RelApproEq +impl, T: RelApproEqWithTol> RelApproEq for T { fn rel_appro_eq(&self, other: &Rhs) -> bool { - self.rel_appro_eq_with_tol(other, &Diff::tolerance()) + self.rel_appro_eq_with_tol(other, &Diff::rel_tolerance()) } } +/// tolerance is 1e-6 for f32 #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] impl Tolerance for f32 { fn tolerance() -> f32 { @@ -264,6 +293,7 @@ impl RelError for f32 { } } +/// tolerance is 1e-6 for f64 #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] impl Tolerance for f64 { fn tolerance() -> f64 { @@ -328,6 +358,7 @@ impl RelError for f64 { macro_rules! itype_impls { ($($T:ty)+) => { $( + /// tolerance is zero for $T #[cfg_attr(feature = "docs", stable(feature = "default", since = "0.1.0"))] impl Tolerance for $T { fn tolerance() -> $T { @@ -595,3 +626,55 @@ impl + ?Sized> RelError, } } +/// absolute tolerance is 1s for Duration +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl AbsTolerance for Duration { + fn abs_tolerance() -> Duration { + Duration::new(1, 0) // 1s + } +} + +/// relative tolerance is 1ms(1/1000) for Duration +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl RelTolerance for Duration { + fn rel_tolerance() -> Duration { + Duration::new(0, 1000000) // 1ms (1/1000) + } +} + +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl AbsError for Instant { + fn abs_error(&self, expected: &Instant) -> ApproEqResult { + Ok(Some(if *self > *expected { *self - *expected } else { *expected - *self })) + } +} + +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl AbsError for SystemTime { + fn abs_error(&self, expected: &SystemTime) -> ApproEqResult { + match if *self > *expected { self.duration_since(*expected) } else { expected.duration_since(*self) } { + Ok(v) => Ok(Some(v)), + Err(e) => Err(ApproEqError::ComponentError(Box::new(e) as Box)), + } + } +} + +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl AbsError for Duration { + fn abs_error(&self, expected: &Duration) -> ApproEqResult { + Ok(Some(if *self > *expected { *self - *expected } else { *expected - *self })) + } +} + +#[cfg_attr(feature = "docs", stable(feature = "default", since = "0.2.0"))] +impl RelError for Duration { + fn rel_error(&self, expected: &Duration) -> ApproEqResult { + if *expected == Duration::new(0, 0) { + Err(ApproEqError::DividedByZero) + } else if expected.as_secs() > std::u32::MAX as u64 { + Err(ApproEqError::DividedByZero) + } else { + Ok(Some((if *self > *expected { *self - *expected } else { *expected - *self }) / expected.as_secs() as u32)) + } + } +} diff --git a/tests/lib.rs b/tests/lib.rs index 45df3fa..1e1a9ed 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -24,6 +24,7 @@ use ndarray::{ArrayD, IxDyn, arr1, arr2, arr3}; use std::rc::Rc; use std::sync::Arc; use std::cell::{Cell, RefCell}; +use std::time::{Duration, Instant, SystemTime}; macro_rules! panic_test_rel { ($name:ident, $($arg:tt)*) => ( @@ -485,3 +486,55 @@ panic_test_all!( arr3(&[[[1f32, 2.0], [4.0, 5.0]], [[6.0, 7.0], [9.0, 10.001]]]), arr3(&[[[1f64, 2.0], [4.0, 5.0]], [[6.0, 7.0], [9.0, 10.0]]]) ); + +ok_test_none!( + compare_with_systemtime_none, + SystemTime::now(), + SystemTime::now() +); + +ok_test_abs!( + compare_with_systemtime_abs, + SystemTime::now(), + SystemTime::now() +); + +panic_test_none!( + bad_compare_with_systemtime_none, + SystemTime::now() - Duration::new(10, 0), + SystemTime::now() +); + +panic_test_abs!( + bad_compare_with_systemtime_abs, + SystemTime::now() - Duration::new(10, 0), + SystemTime::now() +); + +ok_test_abs!(compare_with_instant_abs, Instant::now(), Instant::now()); + +ok_test_none!(compare_with_instant_none, Instant::now(), Instant::now()); + +panic_test_abs!( + bad_compare_with_instant_abs, + Instant::now() - Duration::new(10, 0), + Instant::now() +); + +panic_test_none!( + bad_compare_with_instant_none, + Instant::now() - Duration::new(10, 0), + Instant::now() +); + +ok_test_all!( + compare_with_duration, + Duration::new(10, 0), + Duration::new(10, 1) +); + +panic_test_all!( + bad_compare_with_duration, + Duration::new(9, 0), + Duration::new(10, 1) +);