Skip to content

Commit 741d005

Browse files
committed
feat(float): is_close Predicate
Fixes #11
1 parent a6dc118 commit 741d005

File tree

5 files changed

+130
-1
lines changed

5 files changed

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

src/predicate/float/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2018 The predicates-rs Project Developers.
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
//! Float Predicates
10+
//!
11+
//! This module contains predicates specifiuc to string handling.
12+
13+
#[cfg(feature = "float-cmp")]
14+
mod close;
15+
#[cfg(feature = "float-cmp")]
16+
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)