Skip to main content

selene_graph/
typed_float_key.rs

1//! Ordered floating-point keys for typed property indexes.
2
3use std::cmp::Ordering;
4use std::hash::{Hash, Hasher};
5
6/// Marker error returned when a raw float cannot be admitted to a not-NaN key.
7#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
8#[error("NaN is not an indexable floating-point value")]
9pub struct NotNanError;
10
11/// `f32` wrapper with total ordering via [`f32::total_cmp`].
12#[derive(Clone, Copy, Debug)]
13pub struct NotNanF32(f32);
14
15impl NotNanF32 {
16    /// Construct an ordered f32 key.
17    ///
18    /// # Errors
19    ///
20    /// Returns [`NotNanError`] when `value` is NaN.
21    pub fn new(value: f32) -> Result<Self, NotNanError> {
22        if value.is_nan() {
23            Err(NotNanError)
24        } else {
25            Ok(Self(value))
26        }
27    }
28
29    /// Return the underlying `f32`.
30    #[must_use]
31    pub const fn get(self) -> f32 {
32        self.0
33    }
34}
35
36impl PartialEq for NotNanF32 {
37    fn eq(&self, rhs: &Self) -> bool {
38        self.0.to_bits() == rhs.0.to_bits()
39    }
40}
41
42impl Eq for NotNanF32 {}
43
44impl PartialOrd for NotNanF32 {
45    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
46        Some(self.cmp(rhs))
47    }
48}
49
50impl Ord for NotNanF32 {
51    fn cmp(&self, rhs: &Self) -> Ordering {
52        self.0.total_cmp(&rhs.0)
53    }
54}
55
56impl Hash for NotNanF32 {
57    fn hash<H: Hasher>(&self, state: &mut H) {
58        self.0.to_bits().hash(state);
59    }
60}
61
62/// `f64` wrapper with total ordering via [`f64::total_cmp`].
63///
64/// The constructor rejects NaN because NaN has no useful equality or range
65/// semantics for a graph property index. `+0.0` and `-0.0` remain distinct
66/// keys because equality and hashing use the underlying bit pattern.
67#[derive(Clone, Copy, Debug)]
68pub struct NotNanF64(f64);
69
70impl NotNanF64 {
71    /// Construct a finite-or-infinite ordered f64 key.
72    ///
73    /// # Errors
74    ///
75    /// Returns [`NotNanError`] when `value` is NaN.
76    pub fn new(value: f64) -> Result<Self, NotNanError> {
77        if value.is_nan() {
78            Err(NotNanError)
79        } else {
80            Ok(Self(value))
81        }
82    }
83
84    /// Return the underlying `f64`.
85    #[must_use]
86    pub const fn get(self) -> f64 {
87        self.0
88    }
89}
90
91impl PartialEq for NotNanF64 {
92    fn eq(&self, rhs: &Self) -> bool {
93        self.0.to_bits() == rhs.0.to_bits()
94    }
95}
96
97impl Eq for NotNanF64 {}
98
99impl PartialOrd for NotNanF64 {
100    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
101        Some(self.cmp(rhs))
102    }
103}
104
105impl Ord for NotNanF64 {
106    fn cmp(&self, rhs: &Self) -> Ordering {
107        self.0.total_cmp(&rhs.0)
108    }
109}
110
111impl Hash for NotNanF64 {
112    fn hash<H: Hasher>(&self, state: &mut H) {
113        self.0.to_bits().hash(state);
114    }
115}