Skip to main content

ferroui_base/utilities/
math_utilities.rs

1//! Math utilities not provided by the standard library.
2
3use super::Decimal;
4use crate::{Point, Vector};
5
6/// Returns the smaller of two doubles; NaN is propagated and `-0.0 < +0.0`.
7///
8/// `f64::min` ignores NaN operands, which is not what the layout and geometry code
9/// ported into this crate expects.
10#[inline]
11pub fn min(val1: f64, val2: f64) -> f64 {
12    if val1 != val2 {
13        if !val1.is_nan() {
14            return if val1 < val2 { val1 } else { val2 };
15        }
16        return val1;
17    }
18    if val1.is_sign_negative() {
19        val1
20    } else {
21        val2
22    }
23}
24
25/// Returns the larger of two doubles; NaN is propagated and `+0.0 > -0.0`.
26#[inline]
27pub fn max(val1: f64, val2: f64) -> f64 {
28    if val1 != val2 {
29        if !val1.is_nan() {
30            return if val2 < val1 { val1 } else { val2 };
31        }
32        return val1;
33    }
34    if val2.is_sign_negative() {
35        val1
36    } else {
37        val2
38    }
39}
40
41/// Returns the smaller of two floats; NaN is propagated and `-0.0 < +0.0`.
42#[inline]
43pub fn min_f32(val1: f32, val2: f32) -> f32 {
44    if val1 != val2 {
45        if !val1.is_nan() {
46            return if val1 < val2 { val1 } else { val2 };
47        }
48        return val1;
49    }
50    if val1.is_sign_negative() {
51        val1
52    } else {
53        val2
54    }
55}
56
57/// Returns the larger of two floats; NaN is propagated and `+0.0 > -0.0`.
58#[inline]
59pub fn max_f32(val1: f32, val2: f32) -> f32 {
60    if val1 != val2 {
61        if !val1.is_nan() {
62            return if val2 < val1 { val1 } else { val2 };
63        }
64        return val1;
65    }
66    if val2.is_sign_negative() {
67        val1
68    } else {
69        val2
70    }
71}
72
73/// Equality in which NaN equals NaN (and `0.0 == -0.0`), as opposed to `==`.
74#[inline]
75pub fn equals(a: f64, b: f64) -> bool {
76    a == b || (a.is_nan() && b.is_nan())
77}
78
79/// Provides math utilities not provided by the standard library.
80pub struct MathUtilities;
81
82impl MathUtilities {
83    /// Smallest value such that `1.0 + DOUBLE_EPSILON != 1.0`.
84    pub const DOUBLE_EPSILON: f64 = f64::EPSILON; // 2.2204460492503131e-16
85
86    pub const FLOAT_EPSILON: f32 = f32::EPSILON; // 1.192092896e-07
87
88    /// Returns whether or not two doubles are "close", i.e. within a relative epsilon
89    /// of each other.
90    #[inline]
91    pub fn are_close(value1: f64, value2: f64) -> bool {
92        // in case they are Infinities (then epsilon check does not work)
93        if value1 == value2 {
94            return true;
95        }
96        let eps = (value1.abs() + value2.abs() + 10.0) * Self::DOUBLE_EPSILON;
97        let delta = value1 - value2;
98        (-eps < delta) && (eps > delta)
99    }
100
101    /// Returns whether or not two doubles are within the fixed epsilon `eps` of each other.
102    #[inline]
103    pub fn are_close_eps(value1: f64, value2: f64, eps: f64) -> bool {
104        // in case they are Infinities (then epsilon check does not work)
105        if value1 == value2 {
106            return true;
107        }
108        let delta = value1 - value2;
109        (-eps < delta) && (eps > delta)
110    }
111
112    /// Returns whether or not two floats are "close".
113    #[inline]
114    pub fn are_close_f32(value1: f32, value2: f32) -> bool {
115        // in case they are Infinities (then epsilon check does not work)
116        if value1 == value2 {
117            return true;
118        }
119        let eps = (value1.abs() + value2.abs() + 10.0) * Self::FLOAT_EPSILON;
120        let delta = value1 - value2;
121        (-eps < delta) && (eps > delta)
122    }
123
124    /// Returns whether the first double is strictly less than *and* not close to the second.
125    #[inline]
126    pub fn less_than(value1: f64, value2: f64) -> bool {
127        (value1 < value2) && !Self::are_close(value1, value2)
128    }
129
130    /// Returns whether the first float is strictly less than *and* not close to the second.
131    #[inline]
132    pub fn less_than_f32(value1: f32, value2: f32) -> bool {
133        (value1 < value2) && !Self::are_close_f32(value1, value2)
134    }
135
136    /// Returns whether the first double is strictly greater than *and* not close to the second.
137    #[inline]
138    pub fn greater_than(value1: f64, value2: f64) -> bool {
139        (value1 > value2) && !Self::are_close(value1, value2)
140    }
141
142    /// Returns whether the first float is strictly greater than *and* not close to the second.
143    #[inline]
144    pub fn greater_than_f32(value1: f32, value2: f32) -> bool {
145        (value1 > value2) && !Self::are_close_f32(value1, value2)
146    }
147
148    /// Returns whether the first double is less than or close to the second.
149    #[inline]
150    pub fn less_than_or_close(value1: f64, value2: f64) -> bool {
151        (value1 < value2) || Self::are_close(value1, value2)
152    }
153
154    /// Returns whether the first float is less than or close to the second.
155    #[inline]
156    pub fn less_than_or_close_f32(value1: f32, value2: f32) -> bool {
157        (value1 < value2) || Self::are_close_f32(value1, value2)
158    }
159
160    /// Returns whether the first double is greater than or close to the second.
161    #[inline]
162    pub fn greater_than_or_close(value1: f64, value2: f64) -> bool {
163        (value1 > value2) || Self::are_close(value1, value2)
164    }
165
166    /// Returns whether the first float is greater than or close to the second.
167    #[inline]
168    pub fn greater_than_or_close_f32(value1: f32, value2: f32) -> bool {
169        (value1 > value2) || Self::are_close_f32(value1, value2)
170    }
171
172    /// Returns whether the double is "close" to 1. Same as `are_close(value, 1.0)` but faster.
173    #[inline]
174    pub fn is_one(value: f64) -> bool {
175        (value - 1.0).abs() < 10.0 * Self::DOUBLE_EPSILON
176    }
177
178    /// Returns whether the float is "close" to 1.
179    #[inline]
180    pub fn is_one_f32(value: f32) -> bool {
181        (value - 1.0).abs() < 10.0 * Self::FLOAT_EPSILON
182    }
183
184    /// Returns whether the double is "close" to 0. Same as `are_close(value, 0.0)` but faster.
185    #[inline]
186    pub fn is_zero(value: f64) -> bool {
187        value.abs() < 10.0 * Self::DOUBLE_EPSILON
188    }
189
190    /// Returns whether the float is "close" to 0.
191    #[inline]
192    pub fn is_zero_f32(value: f32) -> bool {
193        value.abs() < 10.0 * Self::FLOAT_EPSILON
194    }
195
196    /// Clamps a value between a minimum and maximum value. NaN is returned unchanged.
197    ///
198    /// # Panics
199    /// Panics if `min > max` (a programmer error).
200    #[inline]
201    pub fn clamp(val: f64, min: f64, max: f64) -> f64 {
202        if min > max {
203            throw_cannot_be_greater_than(min, max);
204        }
205
206        if val < min {
207            min
208        } else if val > max {
209            max
210        } else {
211            val
212        }
213    }
214
215    /// Clamps a value between a minimum and maximum value. The bounds may be given
216    /// in either order.
217    #[inline]
218    pub fn clamp_f32(value: f32, min: f32, max: f32) -> f32 {
219        let amax = max_f32(min, max);
220        let amin = min_f32(min, max);
221        min_f32(max_f32(value, amin), amax)
222    }
223
224    /// Clamps a value between a minimum and maximum value.
225    ///
226    /// # Panics
227    /// Panics if `min > max` (a programmer error).
228    #[inline]
229    pub fn clamp_i32(val: i32, min: i32, max: i32) -> i32 {
230        if min > max {
231            throw_cannot_be_greater_than(min, max);
232        }
233
234        if val < min {
235            min
236        } else if val > max {
237            max
238        } else {
239            val
240        }
241    }
242
243    /// Clamps a value between a minimum and maximum value.
244    ///
245    /// # Panics
246    /// Panics if `min > max` (a programmer error).
247    #[inline]
248    pub fn clamp_decimal(val: Decimal, min: Decimal, max: Decimal) -> Decimal {
249        if min > max {
250            throw_cannot_be_greater_than(min, max);
251        }
252
253        if val < min {
254            min
255        } else if val > max {
256            max
257        } else {
258            val
259        }
260    }
261
262    /// Converts an angle in degrees to radians.
263    #[inline]
264    pub fn deg2rad(angle: f64) -> f64 {
265        angle * (std::f64::consts::PI / 180.0)
266    }
267
268    /// Converts an angle in gradians to radians.
269    #[inline]
270    pub fn grad2rad(angle: f64) -> f64 {
271        angle * (std::f64::consts::PI / 200.0)
272    }
273
274    /// Converts an angle in turns to radians.
275    #[inline]
276    pub fn turn2rad(angle: f64) -> f64 {
277        angle * 2.0 * std::f64::consts::PI
278    }
279
280    /// Calculates the point of an angle (in radians) on an ellipse.
281    #[inline]
282    pub fn get_ellipse_point(centre: Point, radius_x: f64, radius_y: f64, angle: f64) -> Point {
283        Point::new(
284            radius_x * angle.cos() + centre.x,
285            radius_y * angle.sin() + centre.y,
286        )
287    }
288
289    /// Gets the minimum and maximum from the specified numbers.
290    #[inline]
291    pub fn get_min_max(a: f64, b: f64) -> (f64, f64) {
292        if a < b {
293            (a, b)
294        } else {
295            (b, a)
296        }
297    }
298
299    /// Gets the minimum and maximum from the specified number and the difference with that number.
300    #[inline]
301    pub fn get_min_max_from_delta(initial_value: f64, delta: f64) -> (f64, f64) {
302        Self::get_min_max(initial_value, initial_value + delta)
303    }
304
305    /// True for negative values (including `-0.0`), infinities and NaN.
306    #[inline]
307    pub fn is_negative_or_non_finite(d: f64) -> bool {
308        d.to_bits() >= 0x7FF0_0000_0000_0000
309    }
310
311    #[inline]
312    pub fn is_finite(d: f64) -> bool {
313        d.is_finite()
314    }
315
316    /// Classifies on which side of an edge a polygon lies: `1` when all of its
317    /// first `c_poly` points are on the positive side, `-1` when all are on the
318    /// negative side, `0` when it straddles (or touches) the edge.
319    pub fn which_polygon_side_intersects(
320        c_poly: u32,
321        p_pt_poly: &[Vector],
322        pt_current: Vector,
323        vec_edge: Vector,
324    ) -> i32 {
325        let mut n_positive = 0u32;
326        let mut n_negative = 0u32;
327        let mut n_zero = 0u32;
328
329        let vec_edge_normal = Vector::new(-vec_edge.y, vec_edge.x);
330
331        for pt in &p_pt_poly[..c_poly as usize] {
332            let vec_current = pt_current - *pt;
333            let r_dot = Vector::dot(vec_current, vec_edge_normal);
334
335            if r_dot > 0.0 {
336                n_positive += 1;
337            } else if r_dot < 0.0 {
338                n_negative += 1;
339            } else {
340                n_zero += 1;
341            }
342
343            if (n_positive > 0 && n_negative > 0) || (n_zero > 0) {
344                return 0;
345            }
346        }
347
348        if n_positive > 0 {
349            1
350        } else {
351            -1
352        }
353    }
354
355    /// Separating-axis test for two convex polygons.
356    pub fn do_polygons_intersect(
357        c_poly_a: u32,
358        p_pt_poly_a: &[Vector],
359        c_poly_b: u32,
360        p_pt_poly_b: &[Vector],
361    ) -> bool {
362        let (na, nb) = (c_poly_a as usize, c_poly_b as usize);
363
364        for i in 0..na {
365            let vec_edge = p_pt_poly_a[(i + 1) % na] - p_pt_poly_a[i];
366            if Self::which_polygon_side_intersects(c_poly_b, p_pt_poly_b, p_pt_poly_a[i], vec_edge)
367                < 0
368            {
369                return false;
370            }
371        }
372
373        for i in 0..nb {
374            let vec_edge = p_pt_poly_b[(i + 1) % nb] - p_pt_poly_b[i];
375            if Self::which_polygon_side_intersects(c_poly_a, p_pt_poly_a, p_pt_poly_b[i], vec_edge)
376                < 0
377            {
378                return false;
379            }
380        }
381
382        true
383    }
384
385    /// Returns whether polygon A lies entirely inside convex polygon B.
386    pub fn is_entirely_contained(
387        c_poly_a: u32,
388        p_pt_poly_a: &[Vector],
389        c_poly_b: u32,
390        p_pt_poly_b: &[Vector],
391    ) -> bool {
392        let nb = c_poly_b as usize;
393
394        for i in 0..nb {
395            let vec_edge = p_pt_poly_b[(i + 1) % nb] - p_pt_poly_b[i];
396            if Self::which_polygon_side_intersects(c_poly_a, p_pt_poly_a, p_pt_poly_b[i], vec_edge)
397                <= 0
398            {
399                // The whole of the polygon is entirely on the outside of the edge,
400                // so we can never intersect
401                return false;
402            }
403        }
404
405        true
406    }
407}
408
409#[cold]
410#[inline(never)]
411fn throw_cannot_be_greater_than<T: std::fmt::Display>(min: T, max: T) -> ! {
412    panic!("{min} cannot be greater than {max}.");
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    const ANY_VALUE: f64 = 42.42;
420
421    struct Fixture {
422        calculated_any_value: f64,
423        one: f64,
424        zero: f64,
425    }
426
427    fn fixture() -> Fixture {
428        let mut calculated_any_value = 0.0;
429        let mut one = 0.0;
430        let mut zero = 1.0;
431
432        const N: i32 = 10;
433        let dx_any = ANY_VALUE / N as f64;
434        let dx_one = 1.0 / N as f64;
435        let dx_zero = zero / N as f64;
436
437        for _ in 0..N {
438            calculated_any_value += dx_any;
439            one += dx_one;
440            zero -= dx_zero;
441        }
442
443        Fixture {
444            calculated_any_value,
445            one,
446            zero,
447        }
448    }
449
450    /// Rounds to `digits` decimals, ties to even.
451    fn round(value: f64, digits: i32) -> f64 {
452        let power = 10f64.powi(digits);
453        (value * power).round_ties_even() / power
454    }
455
456    #[test]
457    fn two_equivalent_double_values_are_close() {
458        let f = fixture();
459        let actual = MathUtilities::are_close(ANY_VALUE, f.calculated_any_value);
460
461        assert!(actual);
462        assert_eq!(ANY_VALUE, round(f.calculated_any_value, 14));
463    }
464
465    #[test]
466    fn two_equivalent_single_values_are_close() {
467        let f = fixture();
468        let expected_value = ANY_VALUE as f32;
469        let actual_value = f.calculated_any_value as f32;
470
471        let actual = MathUtilities::are_close_f32(expected_value, actual_value);
472
473        assert!(actual);
474        assert_eq!(
475            round(expected_value as f64, 5) as f32,
476            round(actual_value as f64, 4) as f32
477        );
478    }
479
480    #[test]
481    fn calculated_double_one_is_one() {
482        let f = fixture();
483        let actual = MathUtilities::is_one(f.one);
484
485        assert!(actual);
486        assert_eq!(1.0, round(f.one, 15));
487    }
488
489    #[test]
490    fn calculated_single_one_is_one() {
491        let f = fixture();
492        let actual_value = f.one as f32;
493
494        let actual = MathUtilities::is_one_f32(actual_value);
495
496        assert!(actual);
497        assert_eq!(1.0f32, round(actual_value as f64, 7) as f32);
498    }
499
500    #[test]
501    fn calculated_double_zero_is_zero() {
502        let f = fixture();
503        let actual = MathUtilities::is_zero(f.zero);
504
505        assert!(actual);
506        assert_eq!(0.0, round(f.zero, 15));
507    }
508
509    #[test]
510    fn calculated_single_zero_is_zero() {
511        let f = fixture();
512        let actual_value = f.zero as f32;
513
514        let actual = MathUtilities::is_zero_f32(actual_value);
515
516        assert!(actual);
517        assert_eq!(0.0f32, round(actual_value as f64, 7) as f32);
518    }
519
520    #[test]
521    fn float_clamp_input_nan_return_nan() {
522        let clamp = MathUtilities::clamp(f64::NAN, 0.0, 1.0);
523        assert!(clamp.is_nan());
524    }
525
526    #[test]
527    fn float_clamp_input_negative_infinity_return_min() {
528        const MIN: f64 = 0.0;
529        const MAX: f64 = 1.0;
530
531        let actual = MathUtilities::clamp(f64::NEG_INFINITY, MIN, MAX);
532        assert_eq!(MIN, actual);
533    }
534
535    #[test]
536    fn float_clamp_input_positive_infinity_return_max() {
537        const MIN: f64 = 0.0;
538        const MAX: f64 = 1.0;
539
540        let actual = MathUtilities::clamp(f64::INFINITY, MIN, MAX);
541        assert_eq!(MAX, actual);
542    }
543
544    #[test]
545    fn double_float_zero_less_than_one() {
546        assert!(MathUtilities::less_than(0.0, 1.0));
547    }
548
549    #[test]
550    fn single_float_zero_less_than_one() {
551        assert!(MathUtilities::less_than_f32(0.0, 1.0));
552    }
553
554    #[test]
555    fn double_float_one_not_less_than_zero() {
556        assert!(!MathUtilities::less_than(1.0, 0.0));
557    }
558
559    #[test]
560    fn single_float_one_not_less_than_zero() {
561        assert!(!MathUtilities::less_than_f32(1.0, 0.0));
562    }
563
564    #[test]
565    fn double_float_zero_not_greater_than_one() {
566        assert!(!MathUtilities::greater_than(0.0, 1.0));
567    }
568
569    #[test]
570    fn single_float_zero_not_greater_than_one() {
571        assert!(!MathUtilities::greater_than_f32(0.0, 1.0));
572    }
573
574    #[test]
575    fn double_float_one_greater_than_zero() {
576        assert!(MathUtilities::greater_than(1.0, 0.0));
577    }
578
579    #[test]
580    fn single_float_one_greater_than_zero() {
581        assert!(MathUtilities::greater_than_f32(1.0, 0.0));
582    }
583
584    #[test]
585    fn double_float_one_less_than_or_close_one() {
586        assert!(MathUtilities::less_than_or_close(1.0, 1.0));
587    }
588
589    #[test]
590    fn single_float_one_less_than_or_close_one() {
591        assert!(MathUtilities::less_than_or_close_f32(1.0, 1.0));
592    }
593
594    #[test]
595    fn double_float_one_greater_than_or_close_one() {
596        assert!(MathUtilities::greater_than_or_close(1.0, 1.0));
597    }
598
599    #[test]
600    fn single_float_one_greater_than_or_close_one() {
601        assert!(MathUtilities::greater_than_or_close_f32(1.0, 1.0));
602    }
603
604    // Additional coverage (not part of the reference suite).
605
606    #[test]
607    fn min_max_propagate_nan_and_order_zeros() {
608        assert!(min(f64::NAN, 1.0).is_nan());
609        assert!(min(1.0, f64::NAN).is_nan());
610        assert!(max(f64::NAN, 1.0).is_nan());
611        assert!(max(1.0, f64::NAN).is_nan());
612        assert!(min(0.0, -0.0).is_sign_negative());
613        assert!(max(-0.0, 0.0).is_sign_positive());
614        assert_eq!(min(1.0, 2.0), 1.0);
615        assert_eq!(max(1.0, 2.0), 2.0);
616    }
617
618    #[test]
619    fn negative_or_non_finite() {
620        assert!(MathUtilities::is_negative_or_non_finite(-1.0));
621        assert!(MathUtilities::is_negative_or_non_finite(-0.0));
622        assert!(MathUtilities::is_negative_or_non_finite(f64::NAN));
623        assert!(MathUtilities::is_negative_or_non_finite(f64::INFINITY));
624        assert!(!MathUtilities::is_negative_or_non_finite(0.0));
625        assert!(!MathUtilities::is_negative_or_non_finite(1.0));
626    }
627
628    #[test]
629    fn polygon_intersection() {
630        let square = |x: f64, y: f64, s: f64| {
631            [
632                Vector::new(x, y),
633                Vector::new(x, y + s),
634                Vector::new(x + s, y + s),
635                Vector::new(x + s, y),
636            ]
637        };
638        let a = square(0.0, 0.0, 10.0);
639        let b = square(5.0, 5.0, 10.0);
640        let c = square(20.0, 20.0, 5.0);
641        let d = square(2.0, 2.0, 2.0);
642
643        assert!(MathUtilities::do_polygons_intersect(4, &a, 4, &b));
644        assert!(!MathUtilities::do_polygons_intersect(4, &a, 4, &c));
645        assert!(MathUtilities::is_entirely_contained(4, &d, 4, &a));
646        assert!(!MathUtilities::is_entirely_contained(4, &b, 4, &a));
647    }
648
649    // Not from upstream.
650    #[test]
651    fn decimal_clamp_returns_the_value_or_the_nearest_bound() {
652        let decimal = |text: &str| Decimal::parse(text).expect("a decimal");
653        let (min, max) = (decimal("-1.5"), decimal("2.5"));
654
655        assert_eq!(decimal("1"), MathUtilities::clamp_decimal(decimal("1"), min, max));
656        assert_eq!(min, MathUtilities::clamp_decimal(decimal("-2"), min, max));
657        assert_eq!(max, MathUtilities::clamp_decimal(decimal("3"), min, max));
658    }
659
660    // Not from upstream.
661    #[test]
662    #[should_panic(expected = "cannot be greater than")]
663    fn decimal_clamp_throws_when_min_is_greater_than_max() {
664        MathUtilities::clamp_decimal(Decimal::ZERO, Decimal::ONE, Decimal::ZERO);
665    }
666}