1use core::fmt;
2
3use crate::RealError;
4
5#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)]
7pub struct Real {
8 value: f64,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct RealInterval {
14 min: Real,
15 max: Real,
16}
17
18impl Real {
19 #[must_use]
21 pub const fn new(value: f64) -> Self {
22 Self { value }
23 }
24
25 pub const fn try_new(value: f64) -> Result<Self, RealError> {
47 match RealError::validate_value("value", value) {
48 Ok(value) => Ok(Self::new(value)),
49 Err(error) => Err(error),
50 }
51 }
52
53 pub const fn validate(self) -> Result<Self, RealError> {
59 Self::try_new(self.value)
60 }
61
62 #[must_use]
64 pub const fn value(&self) -> f64 {
65 self.value
66 }
67
68 #[must_use]
70 pub const fn zero() -> Self {
71 Self::new(0.0)
72 }
73
74 #[must_use]
76 pub const fn one() -> Self {
77 Self::new(1.0)
78 }
79
80 #[must_use]
82 pub const fn abs(self) -> Self {
83 Self::new(self.value.abs())
84 }
85}
86
87impl fmt::Display for Real {
88 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89 write!(formatter, "{}", self.value)
90 }
91}
92
93impl RealInterval {
94 #[must_use]
96 pub const fn new(min: Real, max: Real) -> Self {
97 Self { min, max }
98 }
99
100 pub fn try_new(min: f64, max: f64) -> Result<Self, RealError> {
117 let min = Real::try_new(min)?;
118 let max = Real::try_new(max)?;
119
120 if min.value() > max.value() {
121 return Err(RealError::InvalidInterval {
122 min: min.value(),
123 max: max.value(),
124 });
125 }
126
127 Ok(Self::new(min, max))
128 }
129
130 pub fn validate(self) -> Result<Self, RealError> {
136 Self::try_new(self.min.value(), self.max.value())
137 }
138
139 #[must_use]
141 pub const fn min(&self) -> Real {
142 self.min
143 }
144
145 #[must_use]
147 pub const fn max(&self) -> Real {
148 self.max
149 }
150
151 #[must_use]
153 pub const fn width(&self) -> f64 {
154 self.max.value() - self.min.value()
155 }
156
157 #[must_use]
159 pub const fn midpoint(&self) -> Real {
160 Real::new(f64::midpoint(self.min.value(), self.max.value()))
161 }
162
163 #[must_use]
165 pub const fn contains(&self, value: Real) -> bool {
166 value.value() >= self.min.value() && value.value() <= self.max.value()
167 }
168
169 #[must_use]
171 pub const fn clamp(&self, value: Real) -> Real {
172 Real::new(value.value().clamp(self.min.value(), self.max.value()))
173 }
174
175 #[must_use]
177 pub const fn is_degenerate(&self) -> bool {
178 self.min.value().to_bits() == self.max.value().to_bits()
179 }
180}
181
182impl fmt::Display for RealInterval {
183 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
184 write!(formatter, "[{}, {}]", self.min, self.max)
185 }
186}
187
188pub fn approx_eq(left: Real, right: Real, tolerance: f64) -> Result<bool, RealError> {
207 let tolerance = RealError::validate_tolerance(tolerance)?;
208
209 Ok((left.value() - right.value()).abs() <= tolerance)
210}
211
212#[cfg(test)]
213mod tests {
214 use super::{Real, RealInterval, approx_eq};
215 use crate::RealError;
216
217 fn assert_close(left: f64, right: f64, tolerance: f64) {
218 assert!(
219 (left - right).abs() <= tolerance,
220 "expected {left} to be within {tolerance} of {right}"
221 );
222 }
223
224 #[test]
225 fn validates_real_values() {
226 assert!(matches!(
227 Real::try_new(f64::NAN),
228 Err(RealError::NonFiniteValue { .. })
229 ));
230 }
231
232 #[test]
233 fn exposes_abs_zero_and_one() -> Result<(), RealError> {
234 let value = Real::try_new(-3.5)?;
235
236 assert_eq!(value.abs(), Real::try_new(3.5)?);
237 assert_eq!(Real::zero(), Real::try_new(0.0)?);
238 assert_eq!(Real::one(), Real::try_new(1.0)?);
239
240 Ok(())
241 }
242
243 #[test]
244 fn compares_values_with_explicit_tolerance() -> Result<(), RealError> {
245 let left = Real::try_new(1.0)?;
246 let right = Real::try_new(1.0 + 1.0e-10)?;
247
248 assert!(approx_eq(left, right, 1.0e-9)?);
249 assert!(!approx_eq(left, right, 1.0e-12)?);
250
251 Ok(())
252 }
253
254 #[test]
255 fn validates_interval_bounds() {
256 assert!(matches!(
257 RealInterval::try_new(2.0, -2.0),
258 Err(RealError::InvalidInterval { .. })
259 ));
260 }
261
262 #[test]
263 fn computes_interval_properties() -> Result<(), RealError> {
264 let interval = RealInterval::try_new(-2.0, 6.0)?;
265
266 assert_close(interval.width(), 8.0, 1.0e-12);
267 assert_close(interval.midpoint().value(), 2.0, 1.0e-12);
268 assert!(interval.contains(Real::try_new(1.5)?));
269 assert_eq!(interval.clamp(Real::try_new(8.0)?), Real::try_new(6.0)?);
270 assert!(!interval.is_degenerate());
271
272 Ok(())
273 }
274}