1use std::cmp::Ordering;
7use std::marker::PhantomData;
8
9pub trait Bounds {
12 const MIN: f32;
13 const MAX: f32;
14}
15
16pub struct Bounded<B: Bounds>(f32, PhantomData<B>);
17
18impl<B: Bounds> Bounded<B> {
19 pub const fn new(value: f32) -> Self {
21 let value = if value.is_nan() || value < B::MIN {
22 B::MIN
23 } else if value > B::MAX {
24 B::MAX
25 } else {
26 value
27 };
28 Self(value, PhantomData)
29 }
30
31 pub const fn get(self) -> f32 {
32 self.0
33 }
34
35 pub const fn min_value() -> Self {
36 Self::new(B::MIN)
37 }
38
39 pub const fn max_value() -> Self {
40 Self::new(B::MAX)
41 }
42}
43
44impl<B: Bounds> Copy for Bounded<B> {}
47
48impl<B: Bounds> Clone for Bounded<B> {
49 fn clone(&self) -> Self {
50 *self
51 }
52}
53
54impl<B: Bounds> std::fmt::Debug for Bounded<B> {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "{:?}", self.0)
57 }
58}
59
60impl<B: Bounds> PartialEq for Bounded<B> {
61 fn eq(&self, other: &Self) -> bool {
62 self.0 == other.0
63 }
64}
65
66impl<B: Bounds> Eq for Bounded<B> {}
69
70impl<B: Bounds> PartialOrd for Bounded<B> {
71 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
72 Some(self.cmp(other))
73 }
74}
75
76impl<B: Bounds> Ord for Bounded<B> {
77 fn cmp(&self, other: &Self) -> Ordering {
78 self.0.total_cmp(&other.0)
79 }
80}
81
82impl<B: Bounds> From<Bounded<B>> for f32 {
83 fn from(value: Bounded<B>) -> f32 {
84 value.0
85 }
86}
87
88impl<B: Bounds> std::ops::Add for Bounded<B> {
89 type Output = Self;
90 fn add(self, rhs: Self) -> Self {
91 Self::new(self.0 + rhs.0)
92 }
93}
94
95impl<B: Bounds> std::ops::Sub for Bounded<B> {
96 type Output = Self;
97 fn sub(self, rhs: Self) -> Self {
98 Self::new(self.0 - rhs.0)
99 }
100}
101
102impl<B: Bounds> std::ops::Mul<f32> for Bounded<B> {
103 type Output = Self;
104 fn mul(self, rhs: f32) -> Self {
105 Self::new(self.0 * rhs)
106 }
107}
108
109impl<B: Bounds> std::ops::Div<f32> for Bounded<B> {
110 type Output = Self;
111 fn div(self, rhs: f32) -> Self {
112 Self::new(self.0 / rhs)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 struct UnitBounds;
123 impl Bounds for UnitBounds {
124 const MIN: f32 = 0.0;
125 const MAX: f32 = 1.0;
126 }
127 type Unit = Bounded<UnitBounds>;
128
129 struct WideBounds;
130 impl Bounds for WideBounds {
131 const MIN: f32 = -10.0;
132 const MAX: f32 = f32::INFINITY;
133 }
134 type Wide = Bounded<WideBounds>;
135
136 #[test]
137 fn construction_clamps_into_the_bounds() {
138 assert_eq!(Unit::new(0.5).get(), 0.5);
139 assert_eq!(Unit::new(-3.0).get(), 0.0);
140 assert_eq!(Unit::new(7.0).get(), 1.0);
141 assert_eq!(Unit::new(f32::NEG_INFINITY).get(), 0.0);
142 assert_eq!(Unit::new(f32::INFINITY).get(), 1.0);
143 }
144
145 #[test]
146 fn nan_collapses_to_the_minimum() {
147 assert_eq!(Unit::new(f32::NAN).get(), 0.0);
148 assert_eq!(Wide::new(f32::NAN).get(), -10.0);
149 }
150
151 #[test]
152 fn an_unbounded_maximum_passes_values_through() {
153 assert_eq!(Wide::new(1e30).get(), 1e30);
154 assert_eq!(Wide::new(f32::INFINITY).get(), f32::INFINITY);
155 }
156
157 #[test]
158 fn addition_and_subtraction_saturate() {
159 assert_eq!(Unit::new(0.75) + Unit::new(0.75), Unit::new(1.0));
160 assert_eq!(Unit::new(0.25) - Unit::new(0.75), Unit::new(0.0));
161 assert_eq!((Unit::new(0.25) + Unit::new(0.5)).get(), 0.75);
162 }
163
164 #[test]
165 fn scaling_clamps_and_survives_nan_producing_math() {
166 assert_eq!((Unit::new(0.5) * 4.0).get(), 1.0);
167 assert_eq!((Unit::new(0.5) * -1.0).get(), 0.0);
168 assert_eq!((Unit::new(0.5) / 0.5).get(), 1.0);
169 assert_eq!((Unit::new(0.0) / 0.0).get(), 0.0);
171 assert_eq!((Unit::new(0.5) * f32::NAN).get(), 0.0);
172 }
173
174 #[test]
175 fn the_order_is_total_and_consistent_with_eq() {
176 let mut values = vec![Unit::new(0.9), Unit::new(0.1), Unit::new(0.5)];
177 values.sort();
178 assert_eq!(values, vec![Unit::new(0.1), Unit::new(0.5), Unit::new(0.9)]);
179 assert!(Unit::new(0.1) < Unit::new(0.2));
180 assert_eq!(Unit::new(2.0), Unit::new(1.0), "both clamp to MAX");
181 assert_eq!(Unit::new(0.3).cmp(&Unit::new(0.3)), Ordering::Equal);
182 }
183
184 #[test]
185 fn min_and_max_value_bracket_every_construction() {
186 assert_eq!(Unit::min_value().get(), 0.0);
187 assert_eq!(Unit::max_value().get(), 1.0);
188 assert!(Unit::min_value() <= Unit::new(-100.0));
189 assert!(Unit::max_value() >= Unit::new(100.0));
190 }
191
192 #[test]
193 fn into_f32_returns_the_clamped_value() {
194 let x: f32 = Unit::new(3.0).into();
195 assert_eq!(x, 1.0);
196 }
197
198 #[test]
199 fn copy_works_without_marker_derives() {
200 let a = Unit::new(0.4);
201 let b = a; assert_eq!(a, b);
203 }
204}