ezno_checker/utilities/
float_range.rs

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
42// TODO try_from (assert ceiling > floor etc)
43impl 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	/// For disjointness. TODO Think this is correct
98	#[must_use]
99	pub fn overlaps(self, other: Self) -> bool {
100		crate::utilities::notify!("{:?} ∩ {:?} != ∅", self, other);
101
102		// TODO more with inclusivity etc
103		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	/// The ⊆ relation (non-strict). For subtyping. TODO Think this is correct
123	#[must_use]
124	pub fn contained_in(self, other: Self) -> bool {
125		crate::utilities::notify!("{:?} ⊆ {:?}", self, other);
126		// Edge case
127		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	/// ∀ a in self, ∀ b in other: a > b
141	#[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	/// ∀ a in self, ∀ b in other: a < b
152	#[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		// there may be a faster way but being lazy
177		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		// TODO >= ?
195		ceiling.floor() > *floor
196	}
197
198	// This will try to get cover
199	// A union like above might create gaps. aka if try_get_cover (0, 1) (3, 4) = (0, 4) then it implies 2
200	// exists is in one of the ranges. Thus in this case it returns None
201	#[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	// TODO more :)
213}
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// TODO more
231#[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())); // 32
296		assert!(!lhs.contains_multiple_of(7.into())); // 28 -- 35
297	}
298}