1use core::fmt;
2
3use use_interval::Interval;
4
5use crate::RealError;
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)]
9pub struct Real {
10 value: f64,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct RealInterval {
16 min: Real,
17 max: Real,
18 interval: Interval<Real>,
19}
20
21impl Real {
22 #[must_use]
24 pub const fn new(value: f64) -> Self {
25 Self { value }
26 }
27
28 pub const fn try_new(value: f64) -> Result<Self, RealError> {
50 match RealError::validate_value("value", value) {
51 Ok(value) => Ok(Self::new(value)),
52 Err(error) => Err(error),
53 }
54 }
55
56 pub const fn validate(self) -> Result<Self, RealError> {
62 Self::try_new(self.value)
63 }
64
65 #[must_use]
67 pub const fn value(&self) -> f64 {
68 self.value
69 }
70
71 #[must_use]
73 pub const fn zero() -> Self {
74 Self::new(0.0)
75 }
76
77 #[must_use]
79 pub const fn one() -> Self {
80 Self::new(1.0)
81 }
82
83 #[must_use]
85 pub const fn abs(self) -> Self {
86 Self::new(self.value.abs())
87 }
88}
89
90impl fmt::Display for Real {
91 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(formatter, "{}", self.value)
93 }
94}
95
96impl RealInterval {
97 #[must_use]
99 pub const fn new(min: Real, max: Real) -> Self {
100 Self {
101 min,
102 max,
103 interval: Interval::closed(min, max),
104 }
105 }
106
107 pub fn try_new(min: f64, max: f64) -> Result<Self, RealError> {
124 let min = Real::try_new(min)?;
125 let max = Real::try_new(max)?;
126
127 if min.value() > max.value() {
128 return Err(RealError::InvalidInterval {
129 min: min.value(),
130 max: max.value(),
131 });
132 }
133
134 Ok(Self::new(min, max))
135 }
136
137 pub fn validate(self) -> Result<Self, RealError> {
143 Self::try_new(self.min.value(), self.max.value())
144 }
145
146 #[must_use]
148 pub const fn min(&self) -> Real {
149 self.min
150 }
151
152 #[must_use]
154 pub const fn max(&self) -> Real {
155 self.max
156 }
157
158 #[must_use]
160 pub const fn interval(&self) -> Interval<Real> {
161 self.interval
162 }
163
164 #[must_use]
166 pub const fn width(&self) -> f64 {
167 self.max.value() - self.min.value()
168 }
169
170 #[must_use]
172 pub const fn midpoint(&self) -> Real {
173 Real::new(f64::midpoint(self.min.value(), self.max.value()))
174 }
175
176 #[must_use]
178 pub const fn contains(&self, value: Real) -> bool {
179 value.value() >= self.min.value() && value.value() <= self.max.value()
180 }
181
182 #[must_use]
184 pub const fn clamp(&self, value: Real) -> Real {
185 Real::new(value.value().clamp(self.min.value(), self.max.value()))
186 }
187
188 #[must_use]
190 pub const fn is_degenerate(&self) -> bool {
191 self.min.value().to_bits() == self.max.value().to_bits()
192 }
193}
194
195impl fmt::Display for RealInterval {
196 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
197 write!(formatter, "[{}, {}]", self.min, self.max)
198 }
199}
200
201pub fn approx_eq(left: Real, right: Real, tolerance: f64) -> Result<bool, RealError> {
220 let tolerance = RealError::validate_tolerance(tolerance)?;
221
222 Ok((left.value() - right.value()).abs() <= tolerance)
223}
224
225#[cfg(test)]
226mod tests {
227 use super::{Real, RealInterval, approx_eq};
228 use crate::RealError;
229 use use_interval::Interval;
230
231 fn assert_close(left: f64, right: f64, tolerance: f64) {
232 assert!(
233 (left - right).abs() <= tolerance,
234 "expected {left} to be within {tolerance} of {right}"
235 );
236 }
237
238 #[test]
239 fn validates_real_values() {
240 assert!(matches!(
241 Real::try_new(f64::NAN),
242 Err(RealError::NonFiniteValue { .. })
243 ));
244 }
245
246 #[test]
247 fn exposes_abs_zero_and_one() -> Result<(), RealError> {
248 let value = Real::try_new(-3.5)?;
249
250 assert_eq!(value.abs(), Real::try_new(3.5)?);
251 assert_eq!(Real::zero(), Real::try_new(0.0)?);
252 assert_eq!(Real::one(), Real::try_new(1.0)?);
253
254 Ok(())
255 }
256
257 #[test]
258 fn compares_values_with_explicit_tolerance() -> Result<(), RealError> {
259 let left = Real::try_new(1.0)?;
260 let right = Real::try_new(1.0 + 1.0e-10)?;
261
262 assert!(approx_eq(left, right, 1.0e-9)?);
263 assert!(!approx_eq(left, right, 1.0e-12)?);
264
265 Ok(())
266 }
267
268 #[test]
269 fn validates_interval_bounds() {
270 assert!(matches!(
271 RealInterval::try_new(2.0, -2.0),
272 Err(RealError::InvalidInterval { .. })
273 ));
274 }
275
276 #[test]
277 fn computes_interval_properties() -> Result<(), RealError> {
278 let interval = RealInterval::try_new(-2.0, 6.0)?;
279
280 assert_close(interval.width(), 8.0, 1.0e-12);
281 assert_close(interval.midpoint().value(), 2.0, 1.0e-12);
282 assert_eq!(
283 interval.interval(),
284 Interval::closed(Real::try_new(-2.0)?, Real::try_new(6.0)?)
285 );
286 assert!(interval.contains(Real::try_new(1.5)?));
287 assert_eq!(interval.clamp(Real::try_new(8.0)?), Real::try_new(6.0)?);
288 assert!(!interval.is_degenerate());
289
290 Ok(())
291 }
292}