Skip to content

Commit 49abc8c

Browse files
committed
feat(float): is_close Predicate
Fixes #11
1 parent fef5e76 commit 49abc8c

File tree

5 files changed

+114
-1
lines changed

5 files changed

+114
-1
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ appveyor = { repository = "assert-rs/predicates-rs" }
2020
[dependencies]
2121
difference = { version = "2.0", optional = true }
2222
regex = { version="0.2", optional = true }
23+
float-cmp = { version="0.4", optional = true }
2324

2425
[features]
25-
default = ["difference", "regex"]
26+
default = ["difference", "regex", "float-cmp"]
2627
unstable = []

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787

8888
#[cfg(feature = "difference")]
8989
extern crate difference;
90+
#[cfg(feature = "float-cmp")]
91+
extern crate float_cmp;
9092
#[cfg(feature = "regex")]
9193
extern crate regex;
9294

src/predicate/float/close.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use float_cmp::ApproxEq;
2+
use float_cmp::Ulps;
3+
4+
use Predicate;
5+
6+
/// Predicate that ensures two numbers are "close" enough, understanding that rounding errors
7+
/// occur.
8+
///
9+
/// This is created by the `predicate::float::is_close`.
10+
#[derive(Clone, Debug)]
11+
pub struct IsClosePredicate {
12+
target: f64,
13+
epsilon: f64,
14+
ulps: <f64 as Ulps>::U,
15+
}
16+
17+
impl IsClosePredicate {
18+
/// Set the amount of error allowed.
19+
///
20+
/// Values `1`-`5` should work in most cases. Some times more control is needed and you will
21+
/// need to set `IsClosePredicate::epsilon` separately from `IsClosePredicate::ulps`.
22+
///
23+
/// # Examples
24+
///
25+
/// ```
26+
/// use predicates::predicate::*;
27+
///
28+
/// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
29+
/// let predicate_fn = float::is_close(a).distance(5);
30+
/// ```
31+
pub fn distance(mut self, distance: <f64 as Ulps>::U) -> Self {
32+
self.epsilon = (distance as f64) * ::std::f64::EPSILON;
33+
self.ulps = distance;
34+
self
35+
}
36+
37+
/// Set the absolute deviation allowed.
38+
///
39+
/// This is meant to handle problems near `0`. Values `1.`-`5.` epislons should work in most
40+
/// cases.
41+
///
42+
/// # Examples
43+
///
44+
/// ```
45+
/// use predicates::predicate::*;
46+
///
47+
/// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
48+
/// let predicate_fn = float::is_close(a).epsilon(5.0 * ::std::f64::EPSILON);
49+
/// ```
50+
pub fn epsilon(mut self, epsilon: f64) -> Self {
51+
self.epsilon = epsilon;
52+
self
53+
}
54+
55+
/// Set the relative deviation allowed.
56+
///
57+
/// This is meant to handle large numbers. Values `1`-`5` should work in most cases.
58+
///
59+
/// # Examples
60+
///
61+
/// ```
62+
/// use predicates::predicate::*;
63+
///
64+
/// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
65+
/// let predicate_fn = float::is_close(a).ulps(5);
66+
/// ```
67+
pub fn ulps(mut self, ulps: <f64 as Ulps>::U) -> Self {
68+
self.ulps = ulps;
69+
self
70+
}
71+
}
72+
73+
impl Predicate for IsClosePredicate {
74+
type Item = f64;
75+
76+
fn eval(&self, variable: &f64) -> bool {
77+
variable.approx_eq(&self.target, self.epsilon, self.ulps)
78+
}
79+
}
80+
81+
/// Create a new `Predicate` that ensures two numbers are "close" enough, understanding that
82+
/// rounding errors occur.
83+
///
84+
/// # Examples
85+
///
86+
/// ```
87+
/// use predicates::predicate::*;
88+
///
89+
/// let a = 0.15_f64 + 0.15_f64 + 0.15_f64;
90+
/// let b = 0.1_f64 + 0.1_f64 + 0.25_f64;
91+
/// let predicate_fn = float::is_close(a);
92+
/// assert_eq!(true, predicate_fn.eval(&b));
93+
/// assert_eq!(false, predicate_fn.distance(0).eval(&b));
94+
/// ```
95+
pub fn is_close(target: f64) -> IsClosePredicate {
96+
IsClosePredicate {
97+
target,
98+
epsilon: 2.0 * ::std::f64::EPSILON,
99+
ulps: 2,
100+
}
101+
}

src/predicate/float/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Float Predicates
2+
//!
3+
//! This module contains predicates specifiuc to string handling.
4+
5+
#[cfg(feature = "float-cmp")]
6+
mod close;
7+
#[cfg(feature = "float-cmp")]
8+
pub use self::close::{is_close, IsClosePredicate};

src/predicate/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub use self::set::{contains, contains_hashable, contains_ord, ContainsPredicate
2525
// specialized primitive `Predicate` types
2626
pub mod str;
2727
pub mod path;
28+
pub mod float;
2829

2930
// combinators
3031
mod boolean;

0 commit comments

Comments
 (0)