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