Skip to main content

geometry_strategy/
compare.rs

1//! Point-ordering policies.
2//!
3//! Mirrors `boost/geometry/policies/compare.hpp` and the default Cartesian,
4//! spherical, and geographic strategies selected from
5//! `boost/geometry/strategies/{compare,spherical/compare}.hpp`.
6//!
7//! The default dimension (`-1`) compares lexicographically across the
8//! dimensions shared by both points. A non-negative const parameter compares
9//! only that dimension. Spherical and geographic longitude comparison treats
10//! `-180°` and `180°` as the same meridian, orders the antimeridian after
11//! ordinary longitudes, and ignores longitude differences at a shared pole.
12
13use core::cmp::Ordering;
14
15use geometry_coords::{CoordinateScalar, Rational, RationalInteger};
16use geometry_cs::{
17    AngleUnit, CartesianFamily, CoordinateSystem, Geographic, GeographicFamily, Spherical,
18    SphericalFamily,
19};
20use geometry_tag::SameAs;
21use geometry_trait::Point;
22
23/// Sentinel used by the comparison policies to inspect all shared dimensions.
24pub const ALL_DIMENSIONS: i8 = -1;
25
26/// Sort points in lexicographically ascending order using epsilon equality.
27///
28/// Mirrors `boost::geometry::less` from `policies/compare.hpp:75-265`.
29#[derive(Debug, Default, Clone, Copy)]
30pub struct Less<const DIMENSION: i8 = ALL_DIMENSIONS>;
31
32/// Sort points in lexicographically ascending order using exact equality.
33///
34/// Mirrors `boost::geometry::less_exact` from
35/// `policies/compare.hpp:35-73`.
36#[derive(Debug, Default, Clone, Copy)]
37pub struct LessExact<const DIMENSION: i8 = ALL_DIMENSIONS>;
38
39/// Sort points in lexicographically descending order using epsilon equality.
40///
41/// Mirrors `boost::geometry::greater` from
42/// `policies/compare.hpp:268-367`.
43#[derive(Debug, Default, Clone, Copy)]
44pub struct Greater<const DIMENSION: i8 = ALL_DIMENSIONS>;
45
46/// Test points for coordinate-wise epsilon equality.
47///
48/// Mirrors `boost::geometry::equal_to` from
49/// `policies/compare.hpp:370-470`.
50#[derive(Debug, Default, Clone, Copy)]
51pub struct EqualTo<const DIMENSION: i8 = ALL_DIMENSIONS>;
52
53impl<const DIMENSION: i8> Less<DIMENSION> {
54    /// Return whether `left` precedes `right` under this policy.
55    ///
56    /// # Panics
57    ///
58    /// Panics when `DIMENSION` is neither [`ALL_DIMENSIONS`] nor a dimension
59    /// present in both points.
60    #[inline]
61    #[must_use]
62    #[allow(
63        clippy::unused_self,
64        reason = "value-method policy objects are directly usable as sorting functors"
65    )]
66    pub fn apply<P1, P2>(self, left: &P1, right: &P2) -> bool
67    where
68        P1: Point,
69        P2: Point,
70        <P1::Cs as CoordinateSystem>::Family: ComparisonFamily<P1, P2>,
71    {
72        <<P1::Cs as CoordinateSystem>::Family as ComparisonFamily<P1, P2>>::less(
73            left, right, DIMENSION, false,
74        )
75    }
76}
77
78impl<const DIMENSION: i8> LessExact<DIMENSION> {
79    /// Return whether `left` exactly precedes `right` under this policy.
80    ///
81    /// # Panics
82    ///
83    /// Panics when `DIMENSION` is neither [`ALL_DIMENSIONS`] nor a dimension
84    /// present in both points.
85    #[inline]
86    #[must_use]
87    #[allow(
88        clippy::unused_self,
89        reason = "value-method policy objects are directly usable as sorting functors"
90    )]
91    pub fn apply<P1, P2>(self, left: &P1, right: &P2) -> bool
92    where
93        P1: Point,
94        P2: Point,
95        <P1::Cs as CoordinateSystem>::Family: ComparisonFamily<P1, P2>,
96    {
97        <<P1::Cs as CoordinateSystem>::Family as ComparisonFamily<P1, P2>>::less(
98            left, right, DIMENSION, true,
99        )
100    }
101}
102
103impl<const DIMENSION: i8> Greater<DIMENSION> {
104    /// Return whether `left` follows `right` under this policy.
105    ///
106    /// # Panics
107    ///
108    /// Panics when `DIMENSION` is neither [`ALL_DIMENSIONS`] nor a dimension
109    /// present in both points.
110    #[inline]
111    #[must_use]
112    #[allow(
113        clippy::unused_self,
114        reason = "value-method policy objects are directly usable as sorting functors"
115    )]
116    pub fn apply<P1, P2>(self, left: &P1, right: &P2) -> bool
117    where
118        P1: Point,
119        P2: Point,
120        <P1::Cs as CoordinateSystem>::Family: ComparisonFamily<P1, P2>,
121    {
122        <<P1::Cs as CoordinateSystem>::Family as ComparisonFamily<P1, P2>>::greater(
123            left, right, DIMENSION,
124        )
125    }
126}
127
128impl<const DIMENSION: i8> EqualTo<DIMENSION> {
129    /// Return whether the selected coordinates compare equal within epsilon.
130    ///
131    /// # Panics
132    ///
133    /// Panics when `DIMENSION` is neither [`ALL_DIMENSIONS`] nor a dimension
134    /// present in both points.
135    #[inline]
136    #[must_use]
137    #[allow(
138        clippy::unused_self,
139        reason = "value-method policy objects are directly usable as sorting functors"
140    )]
141    pub fn apply<P1, P2>(self, left: &P1, right: &P2) -> bool
142    where
143        P1: Point,
144        P2: Point,
145        <P1::Cs as CoordinateSystem>::Family: ComparisonFamily<P1, P2>,
146    {
147        <<P1::Cs as CoordinateSystem>::Family as ComparisonFamily<P1, P2>>::equal(
148            left, right, DIMENSION,
149        )
150    }
151}
152
153/// Coordinate-family dispatch used by [`Less`], [`Greater`], and [`EqualTo`].
154///
155/// This is public only because it appears in the generic policy method bounds;
156/// downstream implementations are sealed.
157#[doc(hidden)]
158pub trait ComparisonFamily<P1: Point, P2: Point>: sealed::ComparisonFamily {
159    #[doc(hidden)]
160    fn less(left: &P1, right: &P2, dimension: i8, exact: bool) -> bool;
161
162    #[doc(hidden)]
163    fn greater(left: &P1, right: &P2, dimension: i8) -> bool;
164
165    #[doc(hidden)]
166    fn equal(left: &P1, right: &P2, dimension: i8) -> bool;
167}
168
169mod sealed {
170    pub trait ComparisonFamily {}
171
172    impl ComparisonFamily for geometry_cs::CartesianFamily {}
173    impl ComparisonFamily for geometry_cs::SphericalFamily {}
174    impl ComparisonFamily for geometry_cs::GeographicFamily {}
175}
176
177impl<P1, P2> ComparisonFamily<P1, P2> for CartesianFamily
178where
179    P1: Point,
180    P2: Point,
181    <P1::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
182    <P2::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
183    P1::Scalar: CoordinateComparison<P2::Scalar>,
184{
185    #[inline]
186    fn less(left: &P1, right: &P2, dimension: i8, exact: bool) -> bool {
187        cartesian_compare(left, right, dimension, Relation::Less, exact)
188    }
189
190    #[inline]
191    fn greater(left: &P1, right: &P2, dimension: i8) -> bool {
192        cartesian_compare(left, right, dimension, Relation::Greater, false)
193    }
194
195    #[inline]
196    fn equal(left: &P1, right: &P2, dimension: i8) -> bool {
197        cartesian_compare(left, right, dimension, Relation::Equal, false)
198    }
199}
200
201impl<P1, P2> ComparisonFamily<P1, P2> for SphericalFamily
202where
203    P1: Point,
204    P2: Point,
205    P1::Cs: AngularCoordinateSystem,
206    P2::Cs: AngularCoordinateSystem,
207    <P1::Cs as CoordinateSystem>::Family: SameAs<SphericalFamily>,
208    <P2::Cs as CoordinateSystem>::Family: SameAs<SphericalFamily>,
209    P1::Scalar: AngularScalar + CoordinateComparison<P2::Scalar>,
210    P2::Scalar: AngularScalar,
211{
212    #[inline]
213    fn less(left: &P1, right: &P2, dimension: i8, exact: bool) -> bool {
214        angular_compare(left, right, dimension, Relation::Less, exact)
215    }
216
217    #[inline]
218    fn greater(left: &P1, right: &P2, dimension: i8) -> bool {
219        angular_compare(left, right, dimension, Relation::Greater, false)
220    }
221
222    #[inline]
223    fn equal(left: &P1, right: &P2, dimension: i8) -> bool {
224        angular_compare(left, right, dimension, Relation::Equal, false)
225    }
226}
227
228impl<P1, P2> ComparisonFamily<P1, P2> for GeographicFamily
229where
230    P1: Point,
231    P2: Point,
232    P1::Cs: AngularCoordinateSystem,
233    P2::Cs: AngularCoordinateSystem,
234    <P1::Cs as CoordinateSystem>::Family: SameAs<GeographicFamily>,
235    <P2::Cs as CoordinateSystem>::Family: SameAs<GeographicFamily>,
236    P1::Scalar: AngularScalar + CoordinateComparison<P2::Scalar>,
237    P2::Scalar: AngularScalar,
238{
239    #[inline]
240    fn less(left: &P1, right: &P2, dimension: i8, exact: bool) -> bool {
241        angular_compare(left, right, dimension, Relation::Less, exact)
242    }
243
244    #[inline]
245    fn greater(left: &P1, right: &P2, dimension: i8) -> bool {
246        angular_compare(left, right, dimension, Relation::Greater, false)
247    }
248
249    #[inline]
250    fn equal(left: &P1, right: &P2, dimension: i8) -> bool {
251        angular_compare(left, right, dimension, Relation::Equal, false)
252    }
253}
254
255#[derive(Clone, Copy)]
256enum Relation {
257    Less,
258    Greater,
259    Equal,
260}
261
262fn cartesian_compare<P1, P2>(
263    left: &P1,
264    right: &P2,
265    dimension: i8,
266    relation: Relation,
267    exact: bool,
268) -> bool
269where
270    P1: Point,
271    P2: Point,
272    P1::Scalar: CoordinateComparison<P2::Scalar>,
273{
274    let shared_dimensions = P1::DIM.min(P2::DIM);
275    validate_dimension(dimension, shared_dimensions);
276
277    if dimension == ALL_DIMENSIONS {
278        for index in 0..shared_dimensions {
279            let result = compare_ordinate(left, right, index, relation, exact);
280            if let Some(result) = result {
281                return result;
282            }
283        }
284        matches!(relation, Relation::Equal)
285    } else {
286        compare_ordinate(
287            left,
288            right,
289            usize::from(dimension.unsigned_abs()),
290            relation,
291            exact,
292        )
293        .unwrap_or(matches!(relation, Relation::Equal))
294    }
295}
296
297fn angular_compare<P1, P2>(
298    left: &P1,
299    right: &P2,
300    dimension: i8,
301    relation: Relation,
302    exact: bool,
303) -> bool
304where
305    P1: Point,
306    P2: Point,
307    P1::Cs: AngularCoordinateSystem,
308    P2::Cs: AngularCoordinateSystem,
309    P1::Scalar: AngularScalar + CoordinateComparison<P2::Scalar>,
310    P2::Scalar: AngularScalar,
311{
312    let shared_dimensions = P1::DIM.min(P2::DIM);
313    validate_dimension(dimension, shared_dimensions);
314
315    if dimension >= 2 {
316        return compare_ordinate(
317            left,
318            right,
319            usize::from(dimension.unsigned_abs()),
320            relation,
321            exact,
322        )
323        .unwrap_or(matches!(relation, Relation::Equal));
324    }
325
326    if dimension == 1 {
327        return angular_latitude(left, right, relation, exact);
328    }
329
330    let longitude = angular_longitude(left, right, relation, exact);
331    if let Some(result) = longitude {
332        return result;
333    }
334    if dimension == 0 {
335        return matches!(relation, Relation::Equal);
336    }
337
338    let latitude = angular_latitude_result(left, right, relation, exact);
339    if let Some(result) = latitude {
340        return result;
341    }
342    for index in 2..shared_dimensions {
343        if let Some(result) = compare_ordinate(left, right, index, relation, exact) {
344            return result;
345        }
346    }
347    matches!(relation, Relation::Equal)
348}
349
350fn angular_longitude<P1, P2>(left: &P1, right: &P2, relation: Relation, exact: bool) -> Option<bool>
351where
352    P1: Point,
353    P2: Point,
354    P1::Cs: AngularCoordinateSystem,
355    P2::Cs: AngularCoordinateSystem,
356    P1::Scalar: AngularScalar,
357    P2::Scalar: AngularScalar,
358{
359    let left_longitude = P1::Cs::to_radians(left.get::<0>());
360    let right_longitude = P2::Cs::to_radians(right.get::<0>());
361    let epsilon = angular_epsilon::<P1::Scalar, P2::Scalar>();
362    let coordinates_equal = values_equal(left_longitude, right_longitude, epsilon, exact);
363    let left_antimeridian = is_antimeridian(left_longitude, epsilon, exact);
364    let right_antimeridian = is_antimeridian(right_longitude, epsilon, exact);
365
366    let left_latitude = P1::Cs::to_radians(left.get::<1>());
367    let right_latitude = P2::Cs::to_radians(right.get::<1>());
368    let same_latitude = values_equal(left_latitude, right_latitude, epsilon, exact);
369    let shared_pole = same_latitude && is_pole(left_latitude, epsilon, exact);
370
371    if coordinates_equal || (left_antimeridian && right_antimeridian) || shared_pole {
372        None
373    } else if left_antimeridian {
374        Some(apply_ordering(Ordering::Greater, relation))
375    } else if right_antimeridian {
376        Some(apply_ordering(Ordering::Less, relation))
377    } else {
378        Some(apply_partial_order(
379            left_longitude.partial_cmp(&right_longitude),
380            relation,
381        ))
382    }
383}
384
385fn angular_latitude<P1, P2>(left: &P1, right: &P2, relation: Relation, exact: bool) -> bool
386where
387    P1: Point,
388    P2: Point,
389    P1::Cs: AngularCoordinateSystem,
390    P2::Cs: AngularCoordinateSystem,
391    P1::Scalar: AngularScalar,
392    P2::Scalar: AngularScalar,
393{
394    angular_latitude_result(left, right, relation, exact)
395        .unwrap_or(matches!(relation, Relation::Equal))
396}
397
398fn angular_latitude_result<P1, P2>(
399    left: &P1,
400    right: &P2,
401    relation: Relation,
402    exact: bool,
403) -> Option<bool>
404where
405    P1: Point,
406    P2: Point,
407    P1::Cs: AngularCoordinateSystem,
408    P2::Cs: AngularCoordinateSystem,
409    P1::Scalar: AngularScalar,
410    P2::Scalar: AngularScalar,
411{
412    let left_latitude = P1::Cs::to_radians(left.get::<1>());
413    let right_latitude = P2::Cs::to_radians(right.get::<1>());
414    let epsilon = angular_epsilon::<P1::Scalar, P2::Scalar>();
415    if values_equal(left_latitude, right_latitude, epsilon, exact) {
416        None
417    } else {
418        Some(apply_partial_order(
419            left_latitude.partial_cmp(&right_latitude),
420            relation,
421        ))
422    }
423}
424
425fn compare_ordinate<P1, P2>(
426    left: &P1,
427    right: &P2,
428    dimension: usize,
429    relation: Relation,
430    exact: bool,
431) -> Option<bool>
432where
433    P1: Point,
434    P2: Point,
435    P1::Scalar: CoordinateComparison<P2::Scalar>,
436{
437    let (ordering, epsilon_equal) = match dimension {
438        0 => left.get::<0>().compare_coordinate(right.get::<0>()),
439        1 => left.get::<1>().compare_coordinate(right.get::<1>()),
440        2 => left.get::<2>().compare_coordinate(right.get::<2>()),
441        3 => left.get::<3>().compare_coordinate(right.get::<3>()),
442        _ => unreachable!("Point dimensions above four are not supported"),
443    };
444    let equal = if exact {
445        ordering == Some(Ordering::Equal)
446    } else {
447        epsilon_equal
448    };
449    if equal {
450        None
451    } else {
452        Some(apply_partial_order(ordering, relation))
453    }
454}
455
456fn apply_partial_order(ordering: Option<Ordering>, relation: Relation) -> bool {
457    ordering.is_some_and(|ordering| apply_ordering(ordering, relation))
458}
459
460fn apply_ordering(ordering: Ordering, relation: Relation) -> bool {
461    match relation {
462        Relation::Less => ordering == Ordering::Less,
463        Relation::Greater => ordering == Ordering::Greater,
464        Relation::Equal => false,
465    }
466}
467
468fn validate_dimension(dimension: i8, shared_dimensions: usize) {
469    assert!(
470        shared_dimensions <= 4,
471        "Point dimensions above four are not supported"
472    );
473    assert!(
474        dimension == ALL_DIMENSIONS
475            || (dimension >= 0 && (dimension.unsigned_abs() as usize) < shared_dimensions),
476        "comparison dimension must be present in both points"
477    );
478}
479
480#[allow(clippy::float_cmp, reason = "exact comparison is a selectable policy")]
481fn values_equal(left: f64, right: f64, epsilon: f64, exact: bool) -> bool {
482    if exact {
483        left == right
484    } else {
485        scaled_equal(left, right, epsilon)
486    }
487}
488
489#[allow(
490    clippy::float_cmp,
491    reason = "exact equality is the required fast path before epsilon scaling"
492)]
493fn scaled_equal(left: f64, right: f64, epsilon: f64) -> bool {
494    left == right
495        || (left.is_finite()
496            && right.is_finite()
497            && (left - right).abs() <= epsilon * left.abs().max(right.abs()).max(1.0))
498}
499
500fn is_antimeridian(value: f64, epsilon: f64, exact: bool) -> bool {
501    values_equal(value.abs(), core::f64::consts::PI, epsilon, exact)
502}
503
504fn is_pole(value: f64, epsilon: f64, exact: bool) -> bool {
505    values_equal(value.abs(), core::f64::consts::FRAC_PI_2, epsilon, exact)
506}
507
508fn angular_epsilon<L: AngularScalar, R: AngularScalar>() -> f64 {
509    match (L::EPSILON, R::EPSILON) {
510        (0.0, right) => right,
511        (left, 0.0) => left,
512        (left, right) => left.min(right),
513    }
514}
515
516/// Scalar conversion used only after angular coordinates are normalized to
517/// radians.
518#[doc(hidden)]
519pub trait AngularScalar: CoordinateScalar {
520    #[doc(hidden)]
521    const EPSILON: f64;
522
523    #[doc(hidden)]
524    fn to_f64(self) -> f64;
525}
526
527impl AngularScalar for f32 {
528    const EPSILON: f64 = f32::EPSILON as f64;
529
530    #[inline]
531    fn to_f64(self) -> f64 {
532        f64::from(self)
533    }
534}
535
536impl AngularScalar for f64 {
537    const EPSILON: f64 = f64::EPSILON;
538
539    #[inline]
540    fn to_f64(self) -> f64 {
541        self
542    }
543}
544
545impl AngularScalar for i32 {
546    const EPSILON: f64 = 0.0;
547
548    #[inline]
549    fn to_f64(self) -> f64 {
550        f64::from(self)
551    }
552}
553
554impl AngularScalar for i64 {
555    const EPSILON: f64 = 0.0;
556
557    #[inline]
558    #[allow(
559        clippy::cast_precision_loss,
560        reason = "angular normalization uses f64 just like Boost calculation promotion"
561    )]
562    fn to_f64(self) -> f64 {
563        self as f64
564    }
565}
566
567impl<I: RationalInteger> AngularScalar for Rational<I> {
568    const EPSILON: f64 = 0.0;
569
570    #[inline]
571    fn to_f64(self) -> f64 {
572        self.to_f64()
573    }
574}
575
576/// Angle-unit extraction for spherical and geographic coordinate systems.
577#[doc(hidden)]
578pub trait AngularCoordinateSystem: CoordinateSystem {
579    #[doc(hidden)]
580    fn to_radians<T: AngularScalar>(value: T) -> f64;
581}
582
583impl<U: AngleUnit> AngularCoordinateSystem for Spherical<U> {
584    #[inline]
585    fn to_radians<T: AngularScalar>(value: T) -> f64 {
586        U::to_radians(value.to_f64())
587    }
588}
589
590impl<U: AngleUnit> AngularCoordinateSystem for Geographic<U> {
591    #[inline]
592    fn to_radians<T: AngularScalar>(value: T) -> f64 {
593        U::to_radians(value.to_f64())
594    }
595}
596
597/// Cross-scalar ordering and epsilon comparison for coordinate values.
598#[doc(hidden)]
599pub trait CoordinateComparison<Rhs: CoordinateScalar>: CoordinateScalar {
600    #[doc(hidden)]
601    fn compare_coordinate(self, right: Rhs) -> (Option<Ordering>, bool);
602}
603
604macro_rules! impl_same_float_comparison {
605    ($type:ty) => {
606        impl CoordinateComparison<$type> for $type {
607            #[inline]
608            fn compare_coordinate(self, right: $type) -> (Option<Ordering>, bool) {
609                (
610                    self.partial_cmp(&right),
611                    scaled_equal(
612                        f64::from(self),
613                        f64::from(right),
614                        f64::from(<$type>::EPSILON),
615                    ),
616                )
617            }
618        }
619    };
620}
621
622impl_same_float_comparison!(f32);
623impl_same_float_comparison!(f64);
624
625macro_rules! impl_same_integer_comparison {
626    ($type:ty) => {
627        impl CoordinateComparison<$type> for $type {
628            #[inline]
629            fn compare_coordinate(self, right: $type) -> (Option<Ordering>, bool) {
630                (Some(self.cmp(&right)), self == right)
631            }
632        }
633    };
634}
635
636impl_same_integer_comparison!(i32);
637impl_same_integer_comparison!(i64);
638
639macro_rules! impl_mixed_float_comparison {
640    ($left:ty, $right:ty, $epsilon:expr) => {
641        impl CoordinateComparison<$right> for $left {
642            #[inline]
643            fn compare_coordinate(self, right: $right) -> (Option<Ordering>, bool) {
644                let left = <$left as AngularScalar>::to_f64(self);
645                let right = <$right as AngularScalar>::to_f64(right);
646                (
647                    left.partial_cmp(&right),
648                    scaled_equal(left, right, $epsilon),
649                )
650            }
651        }
652    };
653}
654
655impl_mixed_float_comparison!(f32, f64, f64::EPSILON);
656impl_mixed_float_comparison!(f64, f32, f64::EPSILON);
657impl_mixed_float_comparison!(i32, f32, f64::EPSILON);
658impl_mixed_float_comparison!(f32, i32, f64::EPSILON);
659impl_mixed_float_comparison!(i32, f64, f64::EPSILON);
660impl_mixed_float_comparison!(f64, i32, f64::EPSILON);
661impl_mixed_float_comparison!(i64, f32, f64::EPSILON);
662impl_mixed_float_comparison!(f32, i64, f64::EPSILON);
663impl_mixed_float_comparison!(i64, f64, f64::EPSILON);
664impl_mixed_float_comparison!(f64, i64, f64::EPSILON);
665
666impl CoordinateComparison<i64> for i32 {
667    #[inline]
668    fn compare_coordinate(self, right: i64) -> (Option<Ordering>, bool) {
669        let left = i64::from(self);
670        (Some(left.cmp(&right)), left == right)
671    }
672}
673
674impl CoordinateComparison<i32> for i64 {
675    #[inline]
676    fn compare_coordinate(self, right: i32) -> (Option<Ordering>, bool) {
677        let right = i64::from(right);
678        (Some(self.cmp(&right)), self == right)
679    }
680}
681
682impl<I, J> CoordinateComparison<Rational<J>> for Rational<I>
683where
684    I: RationalInteger,
685    J: RationalInteger,
686{
687    #[inline]
688    fn compare_coordinate(self, right: Rational<J>) -> (Option<Ordering>, bool) {
689        let left_cross = self.numerator().to_i128() * right.denominator().to_i128();
690        let right_cross = right.numerator().to_i128() * self.denominator().to_i128();
691        let ordering = left_cross.cmp(&right_cross);
692        (Some(ordering), ordering == Ordering::Equal)
693    }
694}
695
696macro_rules! impl_rational_integer_comparison {
697    ($integer:ty) => {
698        impl<I: RationalInteger> CoordinateComparison<$integer> for Rational<I> {
699            #[inline]
700            fn compare_coordinate(self, right: $integer) -> (Option<Ordering>, bool) {
701                let left = self.numerator().to_i128();
702                let right = i128::from(right) * self.denominator().to_i128();
703                let ordering = left.cmp(&right);
704                (Some(ordering), ordering == Ordering::Equal)
705            }
706        }
707
708        impl<I: RationalInteger> CoordinateComparison<Rational<I>> for $integer {
709            #[inline]
710            fn compare_coordinate(self, right: Rational<I>) -> (Option<Ordering>, bool) {
711                let left = i128::from(self) * right.denominator().to_i128();
712                let right = right.numerator().to_i128();
713                let ordering = left.cmp(&right);
714                (Some(ordering), ordering == Ordering::Equal)
715            }
716        }
717    };
718}
719
720impl_rational_integer_comparison!(i32);
721impl_rational_integer_comparison!(i64);
722
723macro_rules! impl_rational_float_comparison {
724    ($float:ty) => {
725        impl<I: RationalInteger> CoordinateComparison<$float> for Rational<I> {
726            #[inline]
727            fn compare_coordinate(self, right: $float) -> (Option<Ordering>, bool) {
728                let left = self.to_f64();
729                let right = f64::from(right);
730                (
731                    left.partial_cmp(&right),
732                    scaled_equal(left, right, f64::from(<$float>::EPSILON)),
733                )
734            }
735        }
736
737        impl<I: RationalInteger> CoordinateComparison<Rational<I>> for $float {
738            #[inline]
739            fn compare_coordinate(self, right: Rational<I>) -> (Option<Ordering>, bool) {
740                let left = f64::from(self);
741                let right = right.to_f64();
742                (
743                    left.partial_cmp(&right),
744                    scaled_equal(left, right, f64::from(<$float>::EPSILON)),
745                )
746            }
747        }
748    };
749}
750
751impl_rational_float_comparison!(f32);
752impl_rational_float_comparison!(f64);