Skip to main content

spectrum_analyzer/
frequency.rs

1/*
2MIT License
3
4Copyright (c) 2023 Philipp Schuster
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24//! Module for [`FiniteF32`] and [`NonNegF32`], and the two convenient type
25//! definitions [`Frequency`] and [`FrequencyValue`] built on them.
26
27use core::cmp::Ordering;
28use core::fmt::{Debug, Display, Formatter, Result};
29use core::ops::{Add, Div, Mul, Neg, Sub};
30
31/// A frequency in Hertz, which is never negative.
32pub type Frequency = NonNegF32;
33/// The value of a [`Frequency`] in a frequency spectrum.
34///
35/// It is the magnitude of the FFT result at that frequency, optionally
36/// scaled. A scaling function can make it negative, for example
37/// [`crate::scaling::scale_20_times_log10`].
38///
39/// See [`crate::samples_fft_to_spectrum`] for what this means in practice.
40pub type FrequencyValue = FiniteF32;
41
42/// Wrapper around [`f32`] that guarantees a finite number, i.e., neither `NaN`
43/// nor infinite. This makes the number orderable and sortable.
44///
45/// The type compares and calculates with [`f32`] directly, so there is rarely
46/// a need to unwrap it:
47///
48/// ```
49/// use spectrum_analyzer::FiniteF32;
50///
51/// let value = FiniteF32::from(0.5);
52/// assert!(value > 0.25);
53/// assert_eq!(value, 0.5);
54/// assert_eq!(value * 2.0, 1.0);
55/// ```
56///
57/// # Panics
58/// Creating a value from an [`f32`] that is not finite panics, and so does an
59/// operation between two values of this type whose result is not finite.
60/// [`Self::try_new`] checks instead.
61#[derive(Copy, Clone, Default)]
62#[repr(transparent)]
63pub struct FiniteF32(f32);
64
65impl FiniteF32 {
66    /// Creates a new value, or `None` if `val` is not finite.
67    #[inline]
68    #[must_use]
69    pub const fn try_new(val: f32) -> Option<Self> {
70        if val.is_finite() {
71            Some(Self(val))
72        } else {
73            None
74        }
75    }
76
77    /// Returns the underlying [`f32`].
78    #[inline]
79    #[must_use]
80    pub const fn val(self) -> f32 {
81        self.0
82    }
83}
84
85impl From<f32> for FiniteF32 {
86    /// # Panics
87    /// If `val` is `NaN` or infinite.
88    #[inline]
89    fn from(val: f32) -> Self {
90        Self::try_new(val).expect("value should be finite")
91    }
92}
93
94impl From<FiniteF32> for f32 {
95    #[inline]
96    fn from(val: FiniteF32) -> Self {
97        val.0
98    }
99}
100
101impl Display for FiniteF32 {
102    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
103        write!(f, "{}", self.0)
104    }
105}
106
107impl Debug for FiniteF32 {
108    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
109        write!(f, "{:?}", self.0)
110    }
111}
112
113impl Ord for FiniteF32 {
114    #[inline]
115    fn cmp(&self, other: &Self) -> Ordering {
116        if self.0 < other.0 {
117            Ordering::Less
118        } else if self.0 == other.0 {
119            Ordering::Equal
120        } else {
121            Ordering::Greater
122        }
123    }
124}
125
126impl Eq for FiniteF32 {}
127
128impl PartialEq for FiniteF32 {
129    #[inline]
130    fn eq(&self, other: &Self) -> bool {
131        self.0 == other.0
132    }
133}
134
135impl PartialOrd for FiniteF32 {
136    #[inline]
137    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
138        Some(self.cmp(other))
139    }
140}
141
142impl PartialEq<f32> for FiniteF32 {
143    #[inline]
144    fn eq(&self, other: &f32) -> bool {
145        self.0 == *other
146    }
147}
148
149impl PartialEq<FiniteF32> for f32 {
150    #[inline]
151    fn eq(&self, other: &FiniteF32) -> bool {
152        *self == other.0
153    }
154}
155
156impl PartialOrd<f32> for FiniteF32 {
157    #[inline]
158    fn partial_cmp(&self, other: &f32) -> Option<Ordering> {
159        self.0.partial_cmp(other)
160    }
161}
162
163impl PartialOrd<FiniteF32> for f32 {
164    #[inline]
165    fn partial_cmp(&self, other: &FiniteF32) -> Option<Ordering> {
166        self.partial_cmp(&other.0)
167    }
168}
169
170impl Neg for FiniteF32 {
171    type Output = Self;
172
173    /// Negating a finite number always yields a finite number.
174    #[inline]
175    fn neg(self) -> Self::Output {
176        Self(-self.0)
177    }
178}
179
180/// Implements an operator for [`FiniteF32`]. Between two of them the guarantee
181/// holds, so the result is wrapped again; with a plain [`f32`] the guarantee is
182/// gone and the result is an [`f32`].
183macro_rules! impl_op {
184    ($trait:ident, $method:ident) => {
185        impl $trait for FiniteF32 {
186            type Output = Self;
187
188            /// # Panics
189            /// If the result is not finite.
190            #[inline]
191            fn $method(self, rhs: Self) -> Self::Output {
192                Self::from($trait::$method(self.0, rhs.0))
193            }
194        }
195
196        impl $trait<f32> for FiniteF32 {
197            type Output = f32;
198
199            #[inline]
200            fn $method(self, rhs: f32) -> Self::Output {
201                $trait::$method(self.0, rhs)
202            }
203        }
204
205        impl $trait<FiniteF32> for f32 {
206            type Output = Self;
207
208            #[inline]
209            fn $method(self, rhs: FiniteF32) -> Self::Output {
210                $trait::$method(self, rhs.0)
211            }
212        }
213    };
214}
215
216impl_op!(Add, add);
217impl_op!(Sub, sub);
218impl_op!(Mul, mul);
219impl_op!(Div, div);
220
221/// Wrapper around [`FiniteF32`] that additionally guarantees a number that is
222/// not negative, so `0.0` or higher.
223///
224/// Like [`FiniteF32`], it compares and calculates with [`f32`] directly.
225/// Operations that can leave the range, such as a subtraction or a negation,
226/// return a [`FiniteF32`].
227///
228/// ```
229/// use spectrum_analyzer::NonNegF32;
230///
231/// let value = NonNegF32::from(0.5);
232/// assert_eq!(value, 0.5);
233/// assert!(value > 0.25);
234/// assert_eq!(NonNegF32::from(0.25) - value, -0.25);
235/// ```
236///
237/// # Panics
238/// Creating a value from a number that is negative or not finite panics, and
239/// so does an operation whose result leaves the range.
240/// [`Self::try_new`] checks instead.
241#[derive(Copy, Clone, Default)]
242#[repr(transparent)]
243pub struct NonNegF32(FiniteF32);
244
245impl NonNegF32 {
246    /// Creates a new value, or `None` if `val` is negative or not finite.
247    #[inline]
248    #[must_use]
249    pub const fn try_new(val: f32) -> Option<Self> {
250        match FiniteF32::try_new(val) {
251            Some(val) if val.val() >= 0.0 => Some(Self(val)),
252            _ => None,
253        }
254    }
255
256    /// Returns the underlying [`f32`].
257    #[inline]
258    #[must_use]
259    pub const fn val(self) -> f32 {
260        self.0.val()
261    }
262}
263
264impl From<f32> for NonNegF32 {
265    /// # Panics
266    /// If `val` is negative, `NaN` or infinite.
267    #[inline]
268    fn from(val: f32) -> Self {
269        Self::try_new(val).expect("value should be finite and not negative")
270    }
271}
272
273impl From<FiniteF32> for NonNegF32 {
274    /// # Panics
275    /// If `val` is negative.
276    #[inline]
277    fn from(val: FiniteF32) -> Self {
278        Self::try_new(val.val()).expect("value should not be negative")
279    }
280}
281
282impl From<NonNegF32> for FiniteF32 {
283    #[inline]
284    fn from(val: NonNegF32) -> Self {
285        val.0
286    }
287}
288
289impl From<NonNegF32> for f32 {
290    #[inline]
291    fn from(val: NonNegF32) -> Self {
292        val.val()
293    }
294}
295
296impl Display for NonNegF32 {
297    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
298        Display::fmt(&self.0, f)
299    }
300}
301
302impl Debug for NonNegF32 {
303    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
304        Debug::fmt(&self.0, f)
305    }
306}
307
308impl Ord for NonNegF32 {
309    #[inline]
310    fn cmp(&self, other: &Self) -> Ordering {
311        self.0.cmp(&other.0)
312    }
313}
314
315impl Eq for NonNegF32 {}
316
317impl PartialEq for NonNegF32 {
318    #[inline]
319    fn eq(&self, other: &Self) -> bool {
320        self.0 == other.0
321    }
322}
323
324impl PartialOrd for NonNegF32 {
325    #[inline]
326    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
327        Some(self.cmp(other))
328    }
329}
330
331impl PartialEq<f32> for NonNegF32 {
332    #[inline]
333    fn eq(&self, other: &f32) -> bool {
334        self.0 == *other
335    }
336}
337
338impl PartialEq<NonNegF32> for f32 {
339    #[inline]
340    fn eq(&self, other: &NonNegF32) -> bool {
341        *self == other.0
342    }
343}
344
345impl PartialOrd<f32> for NonNegF32 {
346    #[inline]
347    fn partial_cmp(&self, other: &f32) -> Option<Ordering> {
348        self.0.partial_cmp(other)
349    }
350}
351
352impl PartialOrd<NonNegF32> for f32 {
353    #[inline]
354    fn partial_cmp(&self, other: &NonNegF32) -> Option<Ordering> {
355        self.partial_cmp(&other.0)
356    }
357}
358
359impl Neg for NonNegF32 {
360    type Output = FiniteF32;
361
362    /// Negating leaves the range, so the result is a [`FiniteF32`].
363    #[inline]
364    fn neg(self) -> Self::Output {
365        -self.0
366    }
367}
368
369impl Sub for NonNegF32 {
370    type Output = FiniteF32;
371
372    /// A subtraction can leave the range, so the result is a [`FiniteF32`].
373    /// The difference of two finite numbers of the same sign is finite, so
374    /// this cannot panic.
375    #[inline]
376    fn sub(self, rhs: Self) -> Self::Output {
377        FiniteF32::from(self.val() - rhs.val())
378    }
379}
380
381/// Implements an operator that cannot leave the range of [`NonNegF32`].
382macro_rules! impl_non_neg_op {
383    ($trait:ident, $method:ident) => {
384        impl $trait for NonNegF32 {
385            type Output = Self;
386
387            /// # Panics
388            /// If the result is not finite.
389            #[inline]
390            fn $method(self, rhs: Self) -> Self::Output {
391                Self::from($trait::$method(self.val(), rhs.val()))
392            }
393        }
394
395        impl $trait<f32> for NonNegF32 {
396            type Output = f32;
397
398            #[inline]
399            fn $method(self, rhs: f32) -> Self::Output {
400                $trait::$method(self.val(), rhs)
401            }
402        }
403
404        impl $trait<NonNegF32> for f32 {
405            type Output = Self;
406
407            #[inline]
408            fn $method(self, rhs: NonNegF32) -> Self::Output {
409                $trait::$method(self, rhs.val())
410            }
411        }
412    };
413}
414
415impl_non_neg_op!(Add, add);
416impl_non_neg_op!(Mul, mul);
417impl_non_neg_op!(Div, div);
418
419impl Sub<f32> for NonNegF32 {
420    type Output = f32;
421
422    #[inline]
423    fn sub(self, rhs: f32) -> Self::Output {
424        self.val() - rhs
425    }
426}
427
428impl Sub<NonNegF32> for f32 {
429    type Output = Self;
430
431    #[inline]
432    fn sub(self, rhs: NonNegF32) -> Self::Output {
433        self - rhs.val()
434    }
435}
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440
441    #[test]
442    fn test_finite_f32_construction() {
443        assert_eq!(Some(FiniteF32(0.5)), FiniteF32::try_new(0.5));
444        assert_eq!(None, FiniteF32::try_new(f32::NAN));
445        assert_eq!(None, FiniteF32::try_new(f32::INFINITY));
446        assert_eq!(None, FiniteF32::try_new(f32::NEG_INFINITY));
447        assert_eq!(0.5, f32::from(FiniteF32::from(0.5)));
448    }
449
450    #[test]
451    #[should_panic(expected = "value should be finite")]
452    fn test_finite_f32_rejects_nan() {
453        let _ = FiniteF32::from(f32::NAN);
454    }
455
456    #[test]
457    fn test_finite_f32_compares_with_f32() {
458        let val = FiniteF32::from(0.5);
459
460        assert_eq!(val, 0.5);
461        assert_eq!(0.5, val);
462        assert!(val > 0.25);
463        assert!(0.75 > val);
464        assert!(val < 0.75);
465    }
466
467    #[test]
468    fn test_finite_f32_arithmetic() {
469        let a = FiniteF32::from(3.0);
470        let b = FiniteF32::from(2.0);
471
472        // between two of them the guarantee holds
473        assert_eq!(FiniteF32::from(5.0), a + b);
474        assert_eq!(FiniteF32::from(1.0), a - b);
475        assert_eq!(FiniteF32::from(6.0), a * b);
476        assert_eq!(FiniteF32::from(1.5), a / b);
477
478        // with a plain f32 it does not, so the result is a plain f32
479        assert_eq!(4.0_f32, a + 1.0);
480        assert_eq!(4.0_f32, 1.0 + a);
481        assert_eq!(-3.0_f32, (-a).val());
482
483        // ... which means these cannot panic
484        assert!((f32::MAX + FiniteF32::from(f32::MAX)).is_infinite());
485        assert!((FiniteF32::from(0.0) / 0.0).is_nan());
486    }
487
488    #[test]
489    fn test_non_neg_f32_construction() {
490        assert_eq!(Some(NonNegF32::from(0.0)), NonNegF32::try_new(0.0));
491        assert_eq!(None, NonNegF32::try_new(-0.5));
492        assert_eq!(None, NonNegF32::try_new(f32::NAN));
493        assert_eq!(0.5, f32::from(NonNegF32::from(0.5)));
494        assert_eq!(FiniteF32::from(0.5), FiniteF32::from(NonNegF32::from(0.5)));
495    }
496
497    #[test]
498    #[should_panic(expected = "value should be finite and not negative")]
499    fn test_non_neg_f32_rejects_negative() {
500        let _ = NonNegF32::from(-0.5);
501    }
502
503    #[test]
504    fn test_non_neg_f32_arithmetic() {
505        let a = NonNegF32::from(3.0);
506        let b = NonNegF32::from(2.0);
507
508        // operations that stay in the range keep the type
509        assert_eq!(NonNegF32::from(5.0), a + b);
510        assert_eq!(NonNegF32::from(6.0), a * b);
511        assert_eq!(NonNegF32::from(1.5), a / b);
512
513        // ... the others fall back to the wider type
514        assert_eq!(FiniteF32::from(-1.0), b - a);
515        assert_eq!(FiniteF32::from(-3.0), -a);
516
517        // ... and a plain f32 drops the guarantee entirely
518        assert_eq!(1.0_f32, a - 2.0);
519        assert_eq!(2.0_f32, 5.0 - a);
520    }
521
522    #[test]
523    #[should_panic(expected = "value should be finite")]
524    fn test_finite_f32_arithmetic_overflow_panics() {
525        let max = FiniteF32::from(f32::MAX);
526        let _ = max + max;
527    }
528}