clearcheck/assertions/float/
mod.rs

1use std::fmt::Debug;
2use std::ops::{Range, RangeInclusive};
3
4use crate::matchers::float::{be_nan, be_negative, be_positive, be_zero};
5use crate::matchers::range::{be_in_exclusive_range, be_in_inclusive_range};
6use crate::matchers::{Should, ShouldNot};
7
8/// FloatAssertion enables assertions about various properties of floating-point numbers, considering their inherent imprecision.
9///
10/// It offers a fluent interface for chaining multiple assertions.
11///
12/// # Example
13/// ```
14/// use clearcheck::assertions::float::FloatAssertion;
15///
16/// let value: f64 = 1.34589;
17/// value
18///     .should_not_be_nan()
19///     .should_be_positive()
20///     .should_be_in_inclusive_range_with_tolerance(1.11..=1.3458, 0.23);
21/// ```
22pub trait FloatAssertion<T: num::Float + Default + PartialEq> {
23    /// - Asserts that the floating-point value is NaN (Not a Number).
24    /// - Returns a reference to self for fluent chaining.
25    /// - Panics if the assertion fails.
26    /// # Example
27    /// ```
28    /// use clearcheck::assertions::float::FloatAssertion;
29    ///
30    /// let value: f64 = num::Float::nan();
31    /// value.should_be_nan();
32    /// ```
33    fn should_be_nan(&self) -> &Self;
34
35    /// - Asserts that the floating-point value is not NaN (Not a Number).
36    /// - Returns a reference to self for fluent chaining.
37    /// - Panics if the assertion fails.
38    /// # Example
39    /// ```
40    /// use clearcheck::assertions::float::FloatAssertion;
41    ///
42    /// let value: f64 = 1.23;
43    /// value.should_not_be_nan();
44    /// ```
45    fn should_not_be_nan(&self) -> &Self;
46
47    /// - Asserts that the floating-point value is zero.
48    /// - Returns a reference to self for fluent chaining.
49    /// - Panics if the assertion fails.
50    /// # Example
51    /// ```
52    /// use clearcheck::assertions::float::FloatAssertion;
53    ///
54    /// let value: f64 = 0.0;
55    /// value.should_be_zero();
56    /// ```
57    fn should_be_zero(&self) -> &Self;
58
59    /// - Asserts that the floating-point value is not zero.
60    /// - Returns a reference to self for fluent chaining.
61    /// - Panics if the assertion fails.
62    /// # Example
63    /// ```
64    /// use clearcheck::assertions::float::FloatAssertion;
65    ///
66    /// let value: f64 = 1.23;
67    /// value.should_not_be_zero();
68    /// ```
69    fn should_not_be_zero(&self) -> &Self;
70
71    /// - Asserts that the floating-point value is positive.
72    /// - Returns a reference to self for fluent chaining.
73    /// - Panics if the assertion fails.
74    /// # Example
75    /// ```
76    /// use clearcheck::assertions::float::FloatAssertion;
77    ///
78    /// let value: f64 = 1.24;
79    /// value.should_be_positive();
80    /// ```
81    fn should_be_positive(&self) -> &Self;
82
83    /// - Asserts that the floating-point value is negative.
84    /// - Returns a reference to self for fluent chaining.
85    /// - Panics if the assertion fails.
86    /// # Example
87    /// ```
88    /// use clearcheck::assertions::float::FloatAssertion;
89    ///
90    /// let value: f64 = -1.23;
91    /// value.should_be_negative();
92    /// ```
93    fn should_be_negative(&self) -> &Self;
94
95    /// - Asserts that the floating-point value falls within the given inclusive range with tolerance.
96    /// - Returns a reference to self for fluent chaining.
97    /// - Panics if the assertion fails.
98    /// # Example
99    /// ```
100    /// use clearcheck::assertions::float::FloatAssertion;
101    ///
102    /// let value: f64 = 1.34589;
103    /// value.should_be_in_inclusive_range_with_tolerance(1.11..=1.3458, 0.23);
104    /// ```
105    fn should_be_in_inclusive_range_with_tolerance(
106        &self,
107        range: RangeInclusive<T>,
108        tolerance: T,
109    ) -> &Self;
110
111    /// - Asserts that the floating-point value does not fall within the given inclusive range with tolerance.
112    /// - Returns a reference to self for fluent chaining.
113    /// - Panics if the assertion fails.
114    /// # Example
115    /// ```
116    /// use clearcheck::assertions::float::FloatAssertion;
117    ///
118    /// let value: f64 = 1.34589;
119    /// value.should_not_be_in_inclusive_range_with_tolerance(1.11..=1.12, 0.12);
120    /// ```
121    fn should_not_be_in_inclusive_range_with_tolerance(
122        &self,
123        range: RangeInclusive<T>,
124        tolerance: T,
125    ) -> &Self;
126
127    /// - Asserts that the floating-point value falls within the given exclusive range with tolerance.
128    /// - Returns a reference to self for fluent chaining.
129    /// - Panics if the assertion fails.
130    /// # Example
131    /// ```
132    /// use clearcheck::assertions::float::FloatAssertion;
133    ///
134    /// let value: f64 = 1.34589;
135    /// value.should_be_in_exclusive_range_with_tolerance(1.11..1.3458, 0.23);
136    /// ```
137    fn should_be_in_exclusive_range_with_tolerance(&self, range: Range<T>, tolerance: T) -> &Self;
138
139    /// - Asserts that the floating-point value does not fall within the given exclusive range with tolerance.
140    /// - Returns a reference to self for fluent chaining.
141    /// - Panics if the assertion fails.
142    /// # Example
143    /// ```
144    /// use clearcheck::assertions::float::FloatAssertion;
145    ///
146    /// let value: f64 = 1.34589;
147    /// value.should_not_be_in_exclusive_range_with_tolerance(1.11..1.12, 0.10);
148    /// ```
149    fn should_not_be_in_exclusive_range_with_tolerance(
150        &self,
151        range: Range<T>,
152        tolerance: T,
153    ) -> &Self;
154}
155
156impl<T: num::Float + Debug + Default + PartialEq> FloatAssertion<T> for T {
157    fn should_be_nan(&self) -> &Self {
158        self.should(&be_nan());
159        self
160    }
161
162    fn should_not_be_nan(&self) -> &Self {
163        self.should_not(&be_nan());
164        self
165    }
166
167    fn should_be_zero(&self) -> &Self {
168        self.should(&be_zero());
169        self
170    }
171
172    fn should_not_be_zero(&self) -> &Self {
173        self.should_not(&be_zero());
174        self
175    }
176
177    fn should_be_positive(&self) -> &Self {
178        self.should(&be_positive());
179        self
180    }
181
182    fn should_be_negative(&self) -> &Self {
183        self.should(&be_negative());
184        self
185    }
186
187    fn should_be_in_inclusive_range_with_tolerance(
188        &self,
189        range: RangeInclusive<T>,
190        tolerance: T,
191    ) -> &Self {
192        self.should(&be_in_inclusive_range(RangeInclusive::new(
193            range.start().add(tolerance),
194            range.end().add(tolerance),
195        )));
196        self
197    }
198
199    fn should_not_be_in_inclusive_range_with_tolerance(
200        &self,
201        range: RangeInclusive<T>,
202        tolerance: T,
203    ) -> &Self {
204        self.should_not(&be_in_inclusive_range(RangeInclusive::new(
205            range.start().add(tolerance),
206            range.end().add(tolerance),
207        )));
208        self
209    }
210
211    fn should_be_in_exclusive_range_with_tolerance(&self, range: Range<T>, tolerance: T) -> &Self {
212        let range_with_tolerance = range.start.add(tolerance)..range.end.add(tolerance);
213        self.should(&be_in_exclusive_range(range_with_tolerance));
214        self
215    }
216
217    fn should_not_be_in_exclusive_range_with_tolerance(
218        &self,
219        range: Range<T>,
220        tolerance: T,
221    ) -> &Self {
222        let range_with_tolerance = range.start.add(tolerance)..range.end.add(tolerance);
223        self.should_not(&be_in_exclusive_range(range_with_tolerance));
224        self
225    }
226}
227
228#[cfg(all(test, feature = "num"))]
229mod tests {
230    use crate::assertions::float::FloatAssertion;
231
232    #[test]
233    fn should_be_nan() {
234        let value: f64 = num::Float::nan();
235        value.should_be_nan();
236    }
237
238    #[test]
239    #[should_panic]
240    fn should_be_nan_but_was_not() {
241        let value: f64 = 1.23;
242        value.should_be_nan();
243    }
244
245    #[test]
246    fn should_not_be_nan() {
247        let value: f64 = 1.23;
248        value.should_not_be_nan();
249    }
250
251    #[test]
252    #[should_panic]
253    fn should_not_be_nan_but_was() {
254        let value: f64 = num::Float::nan();
255        value.should_not_be_nan();
256    }
257
258    #[test]
259    fn should_be_zero() {
260        let value: f64 = 0.0;
261        value.should_be_zero();
262    }
263
264    #[test]
265    #[should_panic]
266    fn should_not_be_zero_but_was() {
267        let value: f64 = 0.0;
268        value.should_not_be_zero();
269    }
270
271    #[test]
272    fn should_be_positive() {
273        let value: f64 = 0.123;
274        value.should_be_positive();
275    }
276
277    #[test]
278    fn should_be_negative() {
279        let value: f64 = -1.23;
280        value.should_be_negative();
281    }
282
283    #[test]
284    fn should_be_in_inclusive_range_with_tolerance() {
285        let value: f64 = 8.123;
286        value.should_be_in_inclusive_range_with_tolerance(6.10..=8.10, 0.123);
287    }
288
289    #[test]
290    #[should_panic]
291    fn should_be_in_inclusive_range_with_tolerance_but_was_not() {
292        let value: f64 = 8.423;
293        value.should_be_in_inclusive_range_with_tolerance(6.10..=8.10, 0.123);
294    }
295
296    #[test]
297    fn should_not_be_in_inclusive_range_with_tolerance() {
298        let value: f64 = 8.123;
299        value.should_not_be_in_inclusive_range_with_tolerance(6.10..=7.10, 0.123);
300    }
301
302    #[test]
303    #[should_panic]
304    fn should_not_be_in_inclusive_range_with_tolerance_but_was() {
305        let value: f64 = 8.123;
306        value.should_not_be_in_inclusive_range_with_tolerance(6.10..=8.10, 0.123);
307    }
308
309    #[test]
310    fn should_be_in_exclusive_range_with_tolerance() {
311        let value: f64 = 8.123;
312        value.should_be_in_exclusive_range_with_tolerance(6.10..8.10, 0.123);
313    }
314
315    #[test]
316    #[should_panic]
317    fn should_be_in_exclusive_range_with_tolerance_but_was_not() {
318        let value: f64 = 8.423;
319        value.should_be_in_exclusive_range_with_tolerance(6.10..8.10, 0.123);
320    }
321
322    #[test]
323    fn should_not_be_in_exclusive_range_with_tolerance() {
324        let value: f64 = 8.423;
325        value.should_not_be_in_exclusive_range_with_tolerance(6.10..8.10, 0.123);
326    }
327
328    #[test]
329    #[should_panic]
330    fn should_not_be_in_exclusive_range_with_tolerance_but_was_not() {
331        let value: f64 = 8.123;
332        value.should_not_be_in_exclusive_range_with_tolerance(6.10..8.20, 0.123);
333    }
334}