1type BetterF64 = ordered_float::NotNan<f64>;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum InclusiveExclusive {
5 Inclusive,
6 Exclusive,
7}
8
9use InclusiveExclusive::{Exclusive, Inclusive};
10
11impl InclusiveExclusive {
12 #[must_use]
13 pub fn mix(self, other: Self) -> Self {
14 if let (Inclusive, Inclusive) = (self, other) {
15 Inclusive
16 } else {
17 Exclusive
18 }
19 }
20
21 #[must_use]
22 pub fn is_inclusive(self) -> bool {
23 matches!(self, Inclusive)
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct FloatRange {
29 pub floor: (InclusiveExclusive, BetterF64),
30 pub ceiling: (InclusiveExclusive, BetterF64),
31}
32
33impl Default for FloatRange {
34 fn default() -> Self {
35 Self {
36 floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()),
37 ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()),
38 }
39 }
40}
41
42impl FloatRange {
44 #[must_use]
45 pub fn new_single(on: BetterF64) -> Self {
46 Self { floor: (Inclusive, on), ceiling: (Inclusive, on) }
47 }
48
49 #[must_use]
50 pub fn as_single(self) -> Option<BetterF64> {
51 if let FloatRange { floor: (Inclusive, floor), ceiling: (Inclusive, ceiling) } = self {
52 (floor == ceiling).then_some(floor)
53 } else {
54 None
55 }
56 }
57
58 #[must_use]
59 pub fn new_greater_than(greater_than: BetterF64) -> Self {
60 FloatRange {
61 floor: (Exclusive, greater_than),
62 ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()),
63 }
64 }
65
66 #[must_use]
67 pub fn get_greater_than(self) -> Option<BetterF64> {
68 (self.floor.1 != f64::NEG_INFINITY).then_some(self.floor.1)
69 }
70
71 #[must_use]
72 pub fn new_less_than(less_than: BetterF64) -> Self {
73 FloatRange {
74 floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()),
75 ceiling: (Exclusive, less_than),
76 }
77 }
78
79 #[must_use]
80 pub fn get_less_than(self) -> Option<BetterF64> {
81 (self.ceiling.1 != f64::INFINITY).then_some(self.ceiling.1)
82 }
83
84 #[must_use]
85 pub fn contains(self, value: BetterF64) -> bool {
86 if self.floor.1 < value && value < self.ceiling.1 {
87 true
88 } else if self.floor.1 == value {
89 self.floor.0.is_inclusive()
90 } else if self.ceiling.1 == value {
91 self.ceiling.0.is_inclusive()
92 } else {
93 false
94 }
95 }
96
97 #[must_use]
99 pub fn overlaps(self, other: Self) -> bool {
100 crate::utilities::notify!("{:?} ∩ {:?} != ∅", self, other);
101
102 other.floor.1 <= self.ceiling.1 || other.ceiling.1 <= self.floor.1
104 }
105
106 pub fn intersection(self, other: Self) -> Result<Self, ()> {
107 crate::utilities::notify!("{:?} ∩ {:?}", self, other);
108
109 let max_floor = self.floor.1.max(other.floor.1);
110 let min_ceiling = self.ceiling.1.min(other.ceiling.1);
111
112 if max_floor <= min_ceiling {
113 Ok(Self {
114 floor: (self.floor.0.mix(other.floor.0), max_floor),
115 ceiling: (self.ceiling.0.mix(other.ceiling.0), min_ceiling),
116 })
117 } else {
118 Err(())
119 }
120 }
121
122 #[must_use]
124 pub fn contained_in(self, other: Self) -> bool {
125 crate::utilities::notify!("{:?} ⊆ {:?}", self, other);
126 let lhs = if let (Inclusive, Exclusive) = (self.floor.0, other.floor.0) {
128 self.floor.1 > other.floor.1
129 } else {
130 self.floor.1 >= other.floor.1
131 };
132 let rhs = if let (Inclusive, Exclusive) = (self.ceiling.0, other.ceiling.0) {
133 self.ceiling.1 < other.ceiling.1
134 } else {
135 self.ceiling.1 <= other.ceiling.1
136 };
137 lhs && rhs
138 }
139
140 #[must_use]
142 pub fn above(self, other: Self) -> bool {
143 crate::utilities::notify!("{:?} > {:?}", self, other);
144 if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) {
145 self.floor.1 > other.ceiling.1
146 } else {
147 self.floor.1 >= other.ceiling.1
148 }
149 }
150
151 #[must_use]
153 pub fn below(self, other: Self) -> bool {
154 crate::utilities::notify!("{:?} < {:?}", self, other);
155 if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) {
156 self.ceiling.1 < other.floor.1
157 } else {
158 self.ceiling.1 <= other.floor.1
159 }
160 }
161
162 #[must_use]
163 pub fn space_addition(self, other: Self) -> Self {
164 let floor_bound = self.floor.0.mix(other.floor.0);
165 let ceiling_bound = self.ceiling.0.mix(other.ceiling.0);
166 Self {
167 floor: (floor_bound, self.floor.1 + other.floor.1),
168 ceiling: (ceiling_bound, self.ceiling.1 + other.ceiling.1),
169 }
170 }
171
172 #[must_use]
173 pub fn space_multiplication(self, other: Self) -> Self {
174 let (l_floor, l_ceiling, r_floor, r_ceiling) =
175 (self.floor.1, self.ceiling.1, other.floor.1, other.ceiling.1);
176 let corners =
178 [l_floor * r_floor, l_floor * r_ceiling, r_floor * l_ceiling, l_ceiling * r_ceiling];
179 let floor = *corners.iter().min().unwrap();
180 let ceiling = *corners.iter().max().unwrap();
181
182 let floor_bound = self.floor.0.mix(other.floor.0);
183 let ceiling_bound = self.ceiling.0.mix(other.ceiling.0);
184 Self { floor: (floor_bound, floor), ceiling: (ceiling_bound, ceiling) }
185 }
186
187 #[must_use]
188 pub fn contains_multiple_of(self, multiple_of: BetterF64) -> bool {
189 let (floor, ceiling) = (self.floor.1, self.ceiling.1);
190
191 let floor = floor / multiple_of;
192 let ceiling = ceiling / multiple_of;
193
194 ceiling.floor() > *floor
196 }
197
198 #[must_use]
202 pub fn try_get_cover(self, other: Self) -> Option<Self> {
203 if self.contained_in(other) {
204 Some(other)
205 } else if other.contained_in(self) {
206 Some(self)
207 } else {
208 None
209 }
210 }
211
212 }
214
215impl From<std::ops::Range<BetterF64>> for FloatRange {
216 fn from(range: std::ops::Range<BetterF64>) -> FloatRange {
217 FloatRange { floor: (Exclusive, range.start), ceiling: (Exclusive, range.end) }
218 }
219}
220impl TryFrom<std::ops::Range<f64>> for FloatRange {
221 type Error = ordered_float::FloatIsNan;
222
223 fn try_from(range: std::ops::Range<f64>) -> Result<Self, Self::Error> {
224 let floor = ordered_float::NotNan::new(range.start)?;
225 let ceiling = ordered_float::NotNan::new(range.end)?;
226 Ok(FloatRange { floor: (Exclusive, floor), ceiling: (Exclusive, ceiling) })
227 }
228}
229
230#[cfg(test)]
232mod tests {
233 use super::{BetterF64, FloatRange, InclusiveExclusive};
234
235 fn e(a: f64) -> (InclusiveExclusive, BetterF64) {
236 (InclusiveExclusive::Exclusive, a.try_into().unwrap())
237 }
238
239 fn i(a: f64) -> (InclusiveExclusive, BetterF64) {
240 (InclusiveExclusive::Inclusive, a.try_into().unwrap())
241 }
242
243 #[test]
244 fn contained_in() {
245 let zero_to_four: FloatRange = FloatRange::try_from(0f64..4f64).unwrap();
246 assert!(FloatRange::new_single(2.into()).contained_in(zero_to_four));
247 }
248
249 #[test]
250 fn overlaps() {
251 assert!(FloatRange { floor: e(0.), ceiling: e(4.) }
252 .overlaps(FloatRange { floor: e(2.), ceiling: e(5.) }));
253
254 assert!(!FloatRange { floor: e(0.), ceiling: e(1.) }
255 .overlaps(FloatRange { floor: e(2.), ceiling: e(5.) }));
256 }
257
258 #[test]
259 fn above() {
260 assert!(FloatRange { floor: e(8.), ceiling: e(10.) }
261 .above(FloatRange { floor: e(6.), ceiling: e(7.) }));
262 assert!(!FloatRange { floor: e(0.), ceiling: e(1.) }
263 .above(FloatRange { floor: e(0.), ceiling: e(5.) }));
264 }
265
266 #[test]
267 fn below() {
268 assert!(FloatRange { floor: e(0.), ceiling: e(4.) }
269 .below(FloatRange { floor: e(6.), ceiling: e(7.) }));
270 assert!(!FloatRange { floor: e(0.), ceiling: e(1.) }
271 .below(FloatRange { floor: e(0.), ceiling: e(5.) }));
272 }
273
274 #[test]
275 fn space_addition() {
276 let lhs = FloatRange { floor: e(0.), ceiling: e(4.) }
277 .space_addition(FloatRange { floor: e(6.), ceiling: e(7.) });
278 assert_eq!(lhs, FloatRange { floor: e(6.), ceiling: e(11.) });
279 }
280
281 #[test]
282 fn space_multiplication() {
283 let lhs = FloatRange { floor: e(0.), ceiling: e(4.) }
284 .space_multiplication(FloatRange { floor: e(6.), ceiling: e(7.) });
285 assert_eq!(lhs, FloatRange { floor: e(0.), ceiling: e(28.) });
286
287 let lhs = FloatRange { floor: e(-2.), ceiling: e(4.) }
288 .space_multiplication(FloatRange { floor: e(-10.), ceiling: e(1.) });
289 assert_eq!(lhs, FloatRange { floor: e(-40.), ceiling: e(20.) });
290 }
291
292 #[test]
293 fn multiple_of() {
294 let lhs = FloatRange { floor: e(30.), ceiling: e(34.) };
295 assert!(lhs.contains_multiple_of(4.into())); assert!(!lhs.contains_multiple_of(7.into())); }
298}