relay_metrics/
finite.rs

1use std::cmp::Ordering;
2use std::error::Error;
3use std::hash::{Hash, Hasher};
4use std::num::ParseFloatError;
5use std::str::FromStr;
6use std::{fmt, ops};
7
8use serde::{Deserialize, Serialize};
9
10/// A finite 64-bit floating point type.
11///
12/// This is a restricted version of [`f64`] that does not allow NaN or infinity.
13#[derive(Clone, Copy, Default, PartialEq, Deserialize, Serialize)]
14#[serde(try_from = "f64")]
15#[repr(transparent)]
16pub struct FiniteF64(f64);
17
18impl FiniteF64 {
19    /// Largest finite value.
20    pub const MAX: Self = Self(f64::MAX);
21    /// Smallest finite value.
22    pub const MIN: Self = Self(f64::MIN);
23    /// Smallest positive normal value.
24    pub const EPSILON: Self = Self(f64::EPSILON);
25
26    /// Creates a finite float without checking whether the value is finte. This results in
27    /// undefined behavior if the value is non-finite.
28    ///
29    /// # Safety
30    ///
31    /// The value must not be NaN or infinite.
32    #[must_use]
33    #[inline]
34    pub const unsafe fn new_unchecked(value: f64) -> Self {
35        Self(value)
36    }
37
38    /// Creates a finite float if the value is finite.
39    #[must_use]
40    #[inline]
41    pub fn new(value: f64) -> Option<Self> {
42        if value.is_finite() {
43            Some(Self(value))
44        } else {
45            None
46        }
47    }
48
49    /// Returns the plain [`f64`].
50    #[inline]
51    pub const fn to_f64(self) -> f64 {
52        self.0
53    }
54
55    /// Computes the absolute value of self.
56    pub fn abs(self) -> Self {
57        Self(self.0.abs())
58    }
59
60    /// Returns the maximum of two numbers.
61    pub fn max(self, other: Self) -> Self {
62        Self(self.0.max(other.0))
63    }
64
65    /// Returns the minimum of two numbers.
66    pub fn min(self, other: Self) -> Self {
67        Self(self.0.min(other.0))
68    }
69
70    /// Adds two numbers, saturating at the maximum and minimum representable values.
71    pub fn saturating_add(self, other: Self) -> Self {
72        Self((self.0 + other.0).clamp(f64::MIN, f64::MAX))
73    }
74
75    /// Subtracts two numbers, saturating at the maximum and minimum representable values.
76    pub fn saturating_sub(self, other: Self) -> Self {
77        Self((self.0 - other.0).clamp(f64::MIN, f64::MAX))
78    }
79
80    /// Multiplies two numbers, saturating at the maximum and minimum representable values.
81    pub fn saturating_mul(self, other: Self) -> Self {
82        Self((self.0 * other.0).clamp(f64::MIN, f64::MAX))
83    }
84
85    /// Divides two numbers, saturating at the maximum and minimum representable values.
86    pub fn saturating_div(self, other: Self) -> Self {
87        Self((self.0 / other.0).clamp(f64::MIN, f64::MAX))
88    }
89
90    // NB: There is no saturating_div, since 0/0 is NaN, which is not finite.
91}
92
93impl Eq for FiniteF64 {}
94
95impl PartialOrd for FiniteF64 {
96    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
97        Some(self.cmp(other))
98    }
99}
100
101impl Ord for FiniteF64 {
102    fn cmp(&self, other: &Self) -> Ordering {
103        // Safety: NaN and infinity cannot be constructed from a finite f64.
104        self.0.partial_cmp(&other.0).unwrap_or(Ordering::Less)
105    }
106}
107
108impl Hash for FiniteF64 {
109    fn hash<H: Hasher>(&self, state: &mut H) {
110        // Safety: NaN and infinity cannot be constructed from a finite f64.
111        self.0.to_bits().hash(state)
112    }
113}
114
115impl fmt::Debug for FiniteF64 {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        self.0.fmt(f)
118    }
119}
120
121impl fmt::Display for FiniteF64 {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        self.0.fmt(f)
124    }
125}
126
127impl ops::Add for FiniteF64 {
128    type Output = Option<Self>;
129
130    fn add(self, other: Self) -> Option<Self> {
131        Self::new(self.0 + other.0)
132    }
133}
134
135impl ops::Sub for FiniteF64 {
136    type Output = Option<Self>;
137
138    fn sub(self, other: Self) -> Option<Self> {
139        Self::new(self.0 - other.0)
140    }
141}
142
143impl ops::Mul for FiniteF64 {
144    type Output = Option<Self>;
145
146    fn mul(self, other: Self) -> Option<Self> {
147        Self::new(self.0 * other.0)
148    }
149}
150
151impl ops::Div for FiniteF64 {
152    type Output = Option<Self>;
153
154    fn div(self, other: Self) -> Option<Self> {
155        Self::new(self.0 / other.0)
156    }
157}
158
159impl ops::Rem for FiniteF64 {
160    type Output = Option<Self>;
161
162    fn rem(self, other: Self) -> Option<Self> {
163        Self::new(self.0 % other.0)
164    }
165}
166
167/// Error type returned when conversion to [`FiniteF64`] fails.
168#[derive(Debug, thiserror::Error)]
169#[error("float is nan or infinite")]
170pub struct TryFromFloatError;
171
172impl TryFrom<f64> for FiniteF64 {
173    type Error = TryFromFloatError;
174
175    fn try_from(value: f64) -> Result<Self, Self::Error> {
176        Self::new(value).ok_or(TryFromFloatError)
177    }
178}
179
180impl TryFrom<f32> for FiniteF64 {
181    type Error = TryFromFloatError;
182
183    fn try_from(value: f32) -> Result<Self, Self::Error> {
184        f64::from(value).try_into()
185    }
186}
187
188impl From<i8> for FiniteF64 {
189    fn from(value: i8) -> Self {
190        unsafe { Self::new_unchecked(value.into()) }
191    }
192}
193
194impl From<i16> for FiniteF64 {
195    fn from(value: i16) -> Self {
196        unsafe { Self::new_unchecked(value.into()) }
197    }
198}
199
200impl From<i32> for FiniteF64 {
201    fn from(value: i32) -> Self {
202        unsafe { Self::new_unchecked(value.into()) }
203    }
204}
205
206impl From<u8> for FiniteF64 {
207    fn from(value: u8) -> Self {
208        unsafe { Self::new_unchecked(value.into()) }
209    }
210}
211
212impl From<u16> for FiniteF64 {
213    fn from(value: u16) -> Self {
214        unsafe { Self::new_unchecked(value.into()) }
215    }
216}
217
218impl From<u32> for FiniteF64 {
219    fn from(value: u32) -> Self {
220        unsafe { Self::new_unchecked(value.into()) }
221    }
222}
223
224impl From<FiniteF64> for f64 {
225    fn from(value: FiniteF64) -> Self {
226        value.to_f64()
227    }
228}
229
230#[derive(Debug)]
231enum ParseFiniteFloatErrorKind {
232    Invalid(ParseFloatError),
233    NonFinite(TryFromFloatError),
234}
235
236/// Error type returned when parsing [`FiniteF64`] fails.
237#[derive(Debug)]
238pub struct ParseFiniteFloatError(ParseFiniteFloatErrorKind);
239
240impl fmt::Display for ParseFiniteFloatError {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        match &self.0 {
243            ParseFiniteFloatErrorKind::Invalid(err) => err.fmt(f),
244            ParseFiniteFloatErrorKind::NonFinite(err) => err.fmt(f),
245        }
246    }
247}
248
249impl Error for ParseFiniteFloatError {
250    fn source(&self) -> Option<&(dyn Error + 'static)> {
251        match &self.0 {
252            ParseFiniteFloatErrorKind::Invalid(err) => Some(err),
253            ParseFiniteFloatErrorKind::NonFinite(err) => Some(err),
254        }
255    }
256}
257
258impl From<ParseFloatError> for ParseFiniteFloatError {
259    fn from(err: ParseFloatError) -> Self {
260        Self(ParseFiniteFloatErrorKind::Invalid(err))
261    }
262}
263
264impl From<TryFromFloatError> for ParseFiniteFloatError {
265    fn from(err: TryFromFloatError) -> Self {
266        Self(ParseFiniteFloatErrorKind::NonFinite(err))
267    }
268}
269
270impl FromStr for FiniteF64 {
271    type Err = ParseFiniteFloatError;
272
273    fn from_str(s: &str) -> Result<Self, Self::Err> {
274        Ok(s.parse::<f64>()?.try_into()?)
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_new() {
284        assert_eq!(FiniteF64::new(0.0), Some(FiniteF64(0.0)));
285        assert_eq!(FiniteF64::new(1.0), Some(FiniteF64(1.0)));
286        assert_eq!(FiniteF64::new(-1.0), Some(FiniteF64(-1.0)));
287        assert_eq!(FiniteF64::new(f64::MIN), Some(FiniteF64(f64::MIN)));
288        assert_eq!(FiniteF64::new(f64::MAX), Some(FiniteF64(f64::MAX)));
289        assert_eq!(FiniteF64::new(f64::NAN), None);
290        assert_eq!(FiniteF64::new(f64::INFINITY), None);
291        assert_eq!(FiniteF64::new(f64::NEG_INFINITY), None);
292    }
293
294    #[test]
295    fn test_arithmetics() {
296        assert_eq!(FiniteF64(1.0) + FiniteF64(1.0), Some(FiniteF64(2.0)));
297        assert_eq!(FiniteF64(f64::MAX) + FiniteF64(f64::MAX), None);
298        assert_eq!(FiniteF64(f64::MIN) + FiniteF64(f64::MIN), None);
299
300        assert_eq!(FiniteF64(1.0) - FiniteF64(1.0), Some(FiniteF64(0.0)));
301        assert_eq!(
302            FiniteF64(f64::MAX) - FiniteF64(f64::MAX),
303            Some(FiniteF64(0.0))
304        );
305        assert_eq!(
306            FiniteF64(f64::MIN) - FiniteF64(f64::MIN),
307            Some(FiniteF64(0.0))
308        );
309
310        assert_eq!(FiniteF64(2.0) * FiniteF64(2.0), Some(FiniteF64(4.0)));
311        assert_eq!(FiniteF64(f64::MAX) * FiniteF64(f64::MAX), None);
312        assert_eq!(FiniteF64(f64::MIN) * FiniteF64(f64::MIN), None);
313
314        assert_eq!(FiniteF64(2.0) / FiniteF64(2.0), Some(FiniteF64(1.0)));
315        assert_eq!(FiniteF64(2.0) / FiniteF64(0.0), None); // Infinity
316        assert_eq!(FiniteF64(-2.0) / FiniteF64(0.0), None); // -Infinity
317        assert_eq!(FiniteF64(0.0) / FiniteF64(0.0), None); // NaN
318    }
319
320    #[test]
321    fn test_saturating_add() {
322        assert_eq!(
323            FiniteF64(1.0).saturating_add(FiniteF64(1.0)),
324            FiniteF64(2.0)
325        );
326        assert_eq!(
327            FiniteF64(f64::MAX).saturating_add(FiniteF64(1.0)),
328            FiniteF64(f64::MAX)
329        );
330        assert_eq!(
331            FiniteF64(f64::MIN).saturating_add(FiniteF64(-1.0)),
332            FiniteF64(f64::MIN)
333        );
334    }
335
336    #[test]
337    fn test_saturating_sub() {
338        assert_eq!(
339            FiniteF64(1.0).saturating_sub(FiniteF64(1.0)),
340            FiniteF64(0.0)
341        );
342        assert_eq!(
343            FiniteF64(f64::MAX).saturating_sub(FiniteF64(-1.0)),
344            FiniteF64(f64::MAX)
345        );
346        assert_eq!(
347            FiniteF64(f64::MIN).saturating_sub(FiniteF64(1.0)),
348            FiniteF64(f64::MIN)
349        );
350    }
351
352    #[test]
353    fn test_saturating_mul() {
354        assert_eq!(
355            FiniteF64::from(2).saturating_mul(FiniteF64::from(2)),
356            FiniteF64::from(4)
357        );
358        assert_eq!(
359            FiniteF64(f64::MAX).saturating_mul(FiniteF64::from(2)),
360            FiniteF64(f64::MAX)
361        );
362        assert_eq!(
363            FiniteF64(f64::MIN).saturating_mul(FiniteF64::from(2)),
364            FiniteF64(f64::MIN)
365        );
366    }
367
368    #[test]
369    fn teste_parse() {
370        assert_eq!("0".parse::<FiniteF64>().unwrap(), FiniteF64(0.0));
371        assert_eq!("0.0".parse::<FiniteF64>().unwrap(), FiniteF64(0.0));
372
373        assert!("bla".parse::<FiniteF64>().is_err());
374        assert!("inf".parse::<FiniteF64>().is_err());
375        assert!("-inf".parse::<FiniteF64>().is_err());
376        assert!("NaN".parse::<FiniteF64>().is_err());
377    }
378}