Skip to main content

dsp_fixedpoint/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![doc = include_str!("../README.md")]
3
4mod format;
5mod num_traits_impl;
6mod ops;
7#[cfg(feature = "serde")]
8pub mod serde;
9
10use num_traits::{AsPrimitive, ConstOne, One};
11
12use core::{
13    hash::{Hash, Hasher},
14    marker::PhantomData,
15    num::Wrapping,
16    ops::{Div, Mul, Shl, Shr},
17};
18
19/// Construct a value from the ratio of two raw values.
20///
21/// For positive `F`, `Q<T, A, F>` computes `(numerator << F) / denominator` in
22/// `A`, then narrows to `T`. The denominator must be nonzero and the result must
23/// fit `T`.
24pub trait FromRatio<T> {
25    /// Return `numerator / denominator` in `Self`'s representation.
26    fn from_ratio(numerator: T, denominator: T) -> Self;
27
28    /// Divide several numerators by one denominator.
29    ///
30    /// Implementations may prepare the denominator once; floating-point types
31    /// multiply every numerator by one reciprocal.
32    fn from_ratios<const N: usize>(numerators: [T; N], denominator: T) -> [Self; N]
33    where
34        T: Copy,
35        Self: Sized,
36    {
37        numerators.map(|numerator| Self::from_ratio(numerator, denominator))
38    }
39}
40
41impl FromRatio<f32> for f32 {
42    #[inline]
43    fn from_ratio(numerator: f32, denominator: f32) -> Self {
44        numerator / denominator
45    }
46
47    #[inline]
48    fn from_ratios<const N: usize>(numerators: [f32; N], denominator: f32) -> [Self; N] {
49        let reciprocal = denominator.recip();
50        numerators.map(|numerator| numerator * reciprocal)
51    }
52}
53
54impl FromRatio<f64> for f64 {
55    #[inline]
56    fn from_ratio(numerator: f64, denominator: f64) -> Self {
57        numerator / denominator
58    }
59
60    #[inline]
61    fn from_ratios<const N: usize>(numerators: [f64; N], denominator: f64) -> [Self; N] {
62        let reciprocal = denominator.recip();
63        numerators.map(|numerator| numerator * reciprocal)
64    }
65}
66
67/// Helper trait to unify over missing impl AsPrimitive<f*> for Wrapping<T>
68pub(crate) trait AsFloat: Copy {
69    fn as_f32(self) -> f32;
70    fn as_f64(self) -> f64;
71}
72
73macro_rules! impl_as_float {
74    ($($ty:ty),* $(,)?) => {
75        $(
76            impl AsFloat for $ty {
77                #[inline]
78                fn as_f32(self) -> f32 {
79                    self as f32
80                }
81
82                #[inline]
83                fn as_f64(self) -> f64 {
84                    self as f64
85                }
86            }
87
88            impl AsFloat for Wrapping<$ty> {
89                #[inline]
90                fn as_f32(self) -> f32 {
91                    self.0 as f32
92                }
93
94                #[inline]
95                fn as_f64(self) -> f64 {
96                    self.0 as f64
97                }
98            }
99        )*
100    };
101}
102
103impl_as_float!(i8, i16, i32, i64, u8, u16, u32, u64);
104
105/// Shift summary trait
106///
107/// Wrapping supports `Sh{lr}<usize>` only.
108pub trait Shift: Copy + Shl<usize, Output = Self> + Shr<usize, Output = Self> {
109    /// Signed shift (positive: left)
110    ///
111    /// `x*2**f`
112    ///
113    /// ```
114    /// # use dsp_fixedpoint::Shift;
115    /// assert_eq!(1i32.shs(1), 2);
116    /// assert_eq!(4i32.shs(-1), 2);
117    /// ```
118    fn shs(self, f: i8) -> Self;
119
120    /// Const signed shift
121    #[inline(always)]
122    fn shsc<const F: i8>(self) -> Self {
123        const { assert!(F > i8::MIN, "shift must not be i8::MIN") }
124        self.shs(F)
125    }
126}
127
128impl<T: Copy + Shl<usize, Output = T> + Shr<usize, Output = T>> Shift for T {
129    #[inline(always)]
130    fn shs(self, f: i8) -> Self {
131        debug_assert!(f > i8::MIN, "shift must not be i8::MIN");
132        if f >= 0 {
133            self << (f as _)
134        } else {
135            self >> (-f as _)
136        }
137    }
138}
139
140/// Conversion trait between base and accumulator type
141pub trait Accu<A> {
142    /// Cast up to accumulator type
143    ///
144    /// This is a primitive cast.
145    ///
146    /// ```
147    /// # use dsp_fixedpoint::Accu;
148    /// assert_eq!(3i32.up(), 3i64);
149    /// ```
150    fn up(self) -> A;
151
152    /// Cast down from accumulator type
153    ///
154    /// This is a primitive cast.
155    ///
156    /// ```
157    /// # use dsp_fixedpoint::Accu;
158    /// assert_eq!(i16::down(3i32), 3i16);
159    /// ```
160    fn down(a: A) -> Self;
161
162    // /// Cast to f32
163    // fn as_f32(self) -> f32;
164    // /// Cast to f64
165    // fn as_f64(self) -> f64;
166    // /// Cast from f32
167    // fn f32_as(value: f64) -> Self;
168    // /// Cast from f64
169    // fn f64_as(value: f64) -> Self;
170}
171
172/// Fixed-point value with storage `T`, accumulator `A`, and `F` fractional bits.
173///
174/// Generics:
175/// * `T`: Base integer
176/// * `A`: Accumulator for intermediate results
177/// * `F`: Number of fractional bits right of the decimal point
178///
179/// `F` negative is supported analogously.
180///
181/// * `Q32<31>` is `(-1..1).step_by(2^-31)`
182/// * `Q<i16, _, 20>` is `(-1/32..1/32).step_by(2^-20)`
183/// * `Q<u8, _, 4>` is `(0..16).step_by(1/16)`
184/// * `Q<u8, _, -2>` is `(0..1024).step_by(4)`
185///
186/// ```
187/// # use dsp_fixedpoint::Q8;
188/// assert_eq!(Q8::<4>::from_int(3), Q8::from_bits(3 << 4));
189/// assert_eq!(7 * Q8::<4>::from_f32(1.5), 10);
190/// assert_eq!(Q8::<4>::from_f32(1.5).apply(7), 10);
191/// assert_eq!(7 / Q8::<4>::from_f32(1.5), 4);
192/// ```
193#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
194#[derive(Default)]
195#[repr(transparent)]
196#[cfg_attr(feature = "serde", serde(transparent))]
197#[cfg_attr(
198    feature = "bytemuck",
199    derive(bytemuck::Pod, bytemuck::TransparentWrapper, bytemuck::Zeroable),
200    transparent(T)
201)]
202#[must_use]
203pub struct Q<T, A, const F: i8> {
204    /// The accumulator type
205    _accu: PhantomData<A>,
206    /// The inner value representation
207    inner: T,
208}
209
210impl<T: Clone, A, const F: i8> Clone for Q<T, A, F> {
211    #[inline]
212    fn clone(&self) -> Self {
213        Self {
214            _accu: PhantomData,
215            inner: self.inner.clone(),
216        }
217    }
218}
219
220impl<T: Copy, A, const F: i8> Copy for Q<T, A, F> {}
221
222impl<T: PartialEq, A, const F: i8> PartialEq for Q<T, A, F> {
223    #[inline]
224    fn eq(&self, other: &Self) -> bool {
225        self.inner.eq(&other.inner)
226    }
227}
228
229impl<T: Eq, A, const F: i8> Eq for Q<T, A, F> where Self: PartialEq {}
230
231impl<T: Hash, A, const F: i8> Hash for Q<T, A, F> {
232    #[inline]
233    fn hash<H: Hasher>(&self, state: &mut H) {
234        self.inner.hash(state)
235    }
236}
237
238impl<T: PartialOrd, A, const F: i8> PartialOrd for Q<T, A, F> {
239    #[inline]
240    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
241        self.inner.partial_cmp(&other.inner)
242    }
243}
244
245impl<T: Ord, A, const F: i8> Ord for Q<T, A, F>
246where
247    Self: PartialOrd,
248{
249    #[inline]
250    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
251        self.inner.cmp(&other.inner)
252    }
253}
254
255impl<T, A, const F: i8> Q<T, A, F> {
256    /// Step between distinct numbers
257    ///
258    /// ```
259    /// # use dsp_fixedpoint::Q32;
260    /// assert_eq!(Q32::<31>::DELTA, 2f32.powi(-31));
261    /// assert_eq!(Q32::<-4>::DELTA, 2f32.powi(4));
262    /// ```
263    ///
264    /// ```compile_fail
265    /// # use dsp_fixedpoint::Q32;
266    /// let _ = Q32::<-128>::DELTA;
267    /// ```
268    pub const DELTA: f32 = if F > 0 {
269        1.0 / (1u128 << F) as f32
270    } else {
271        (1u128 << -F) as f32
272    };
273
274    #[inline]
275    const fn new(inner: T) -> Self {
276        Self {
277            _accu: PhantomData,
278            inner,
279        }
280    }
281
282    /// Create a new fixed point number from a raw representation.
283    #[inline]
284    pub const fn from_bits(bits: T) -> Self {
285        Self::new(bits)
286    }
287
288    /// Return the raw representation.
289    #[inline]
290    #[must_use]
291    pub fn into_bits(self) -> T {
292        self.inner
293    }
294}
295
296impl<T: Shift, A, const F: i8> Q<T, A, F> {
297    /// Convert to a different number of fractional bits (truncating)
298    ///
299    /// Use this liberally for Add/Sub/Rem with Q's of different F.
300    ///
301    /// ```
302    /// # use dsp_fixedpoint::Q8;
303    /// assert_eq!(Q8::<4>::from_bits(32).scale::<0>(), Q8::from_bits(2));
304    /// ```
305    #[inline]
306    pub fn scale<const F1: i8>(self) -> Q<T, A, F1> {
307        Q::new(self.inner.shs(const { F1 - F }))
308    }
309
310    /// Return the integer part
311    ///
312    /// ```
313    /// # use dsp_fixedpoint::Q8;
314    /// assert_eq!(Q8::<4>::from_bits(0x35).trunc(), 0x3);
315    /// ```
316    #[inline]
317    #[must_use]
318    pub fn trunc(self) -> T {
319        self.inner.shs(const { -F })
320    }
321
322    /// Scale from integer base type
323    ///
324    /// ```
325    /// # use dsp_fixedpoint::Q8;
326    /// assert_eq!(Q8::<4>::from_int(7).into_bits(), 7 << 4);
327    /// ```
328    #[inline]
329    pub fn from_int(value: T) -> Self {
330        Self::new(value.shsc::<F>())
331    }
332}
333
334impl<A: Shift, T: Accu<A>, const F: i8> Q<A, T, F> {
335    /// Scale from integer accu type
336    ///
337    ///
338    /// ```
339    /// # use dsp_fixedpoint::Q8;
340    /// let q = Q8::<4>::from_f32(0.25);
341    /// assert_eq!((q * 7).quantize(), (7.0 * 0.25f32).floor() as _);
342    /// ```
343    #[inline]
344    #[must_use]
345    pub fn quantize(self) -> T {
346        T::down(self.trunc())
347    }
348}
349
350impl<T: Accu<A>, A: Mul<Output = A>, const F: i8> Q<T, A, F> {
351    /// Multiply a coefficient by a raw value without quantizing.
352    ///
353    /// Accumulate the returned `Q<A, T, F>` values, then call [`Q::quantize`]
354    /// once.
355    ///
356    /// ```
357    /// # use dsp_fixedpoint::{Q, Q8};
358    /// assert_eq!(Q8::<3>::from_bits(4).mul_wide(2), Q::from_bits(8));
359    /// ```
360    #[inline]
361    pub fn mul_wide(self, rhs: T) -> Q<A, T, F> {
362        Q::new(self.inner.up() * rhs.up())
363    }
364}
365
366impl<T, A, const F: i8> FromRatio<T> for Q<T, A, F>
367where
368    T: Copy + Shift + Accu<A> + Div<Output = T>,
369    A: Shift + Div<Output = A>,
370{
371    #[inline]
372    fn from_ratio(numerator: T, denominator: T) -> Self {
373        const { assert!(F > i8::MIN, "fractional bits must not be i8::MIN") }
374        let inner = if F > 0 {
375            T::down(numerator.up().shs(F) / denominator.up())
376        } else {
377            numerator.shs(F) / denominator
378        };
379        Self::new(inner)
380    }
381}
382
383impl<T: Accu<A>, A: Shift + Mul<Output = A>, const F: i8> Q<T, A, F> {
384    /// Apply this fixed-point value as a gain to a raw integer and quantize.
385    ///
386    /// ```
387    /// # use dsp_fixedpoint::Q8;
388    /// assert_eq!(Q8::<4>::from_f32(0.25).apply(7), 1);
389    /// ```
390    #[inline]
391    #[must_use]
392    pub fn apply(self, rhs: T) -> T {
393        self.mul_wide(rhs).quantize()
394    }
395}
396
397/// Lossy conversion from a dynamically scaled integer
398///
399/// ```
400/// # use dsp_fixedpoint::Q8;
401/// assert_eq!(Q8::<8>::from((1, 3)).into_bits(), 1 << 5);
402/// ```
403impl<T: Accu<A> + Shift, A, const F: i8> From<(T, i8)> for Q<T, A, F> {
404    fn from(value: (T, i8)) -> Self {
405        Self::new(value.0.shs(F - value.1))
406    }
407}
408
409/// Lossless conversion into a dynamically scaled integer
410///
411/// ```
412/// # use dsp_fixedpoint::Q8;
413/// let q: (i8, i8) = Q8::<8>::from_bits(9).into();
414/// assert_eq!(q, (9, 8));
415/// ```
416impl<T, A, const F: i8> From<Q<T, A, F>> for (T, i8) {
417    fn from(value: Q<T, A, F>) -> Self {
418        (value.inner, F)
419    }
420}
421
422impl<T, A, const F: i8> Q<T, A, F>
423where
424    f32: AsPrimitive<Q<T, A, F>>,
425    Self: Copy + 'static,
426{
427    /// Quantize a f32
428    #[inline]
429    pub fn from_f32(value: f32) -> Self {
430        value.as_()
431    }
432}
433
434impl<T, A, const F: i8> Q<T, A, F>
435where
436    f64: AsPrimitive<Q<T, A, F>>,
437    Self: Copy + 'static,
438{
439    /// Quantize a f64
440    #[inline]
441    pub fn from_f64(value: f64) -> Self {
442        value.as_()
443    }
444}
445
446#[allow(private_bounds)]
447impl<T: AsFloat, A, const F: i8> Q<T, A, F> {
448    /// Convert lossy to f32
449    #[inline]
450    #[must_use]
451    pub fn as_f32(self) -> f32 {
452        self.inner.as_f32() * Self::DELTA
453    }
454
455    /// Convert lossy to f64
456    #[inline]
457    #[must_use]
458    pub fn as_f64(self) -> f64 {
459        self.inner.as_f64() * Self::DELTA as f64
460    }
461}
462
463macro_rules! impl_q {
464    // Primitive
465    ($alias:ident<$t:ty, $a:ty>) => {
466        impl_q!($alias<$t, $a>, $t, |x| x as _, core::convert::identity);
467    };
468    // Newtype
469    ($alias:ident<$t:ty, $a:ty>, $wrap:tt) => {
470        impl_q!($alias<$wrap<$t>, $wrap<$a>>, $t, |x: $wrap<_>| $wrap(x.0 as _), $wrap);
471    };
472    // Common
473    ($alias:ident<$t:ty, $a:ty>, $inner:ty, $as:expr, $wrap:expr) => {
474        impl Accu<$a> for $t {
475            #[inline(always)]
476            fn up(self) -> $a {
477                $as(self)
478            }
479            #[inline(always)]
480            fn down(a: $a) -> Self {
481                $as(a)
482            }
483        }
484
485        #[doc = concat!("Fixed point [`", stringify!($t), "`] with [`", stringify!($a), "`] accumulator")]
486        pub type $alias<const F: i8> = Q<$t, $a, F>;
487
488        impl<const F: i8> ConstOne for Q<$t, $a, F> {
489            const ONE: Self = {
490                const {
491                    const MAX_ONE_F: i8 =
492                        <$inner>::BITS as i8 - if <$inner>::MIN == 0 { 0 } else { 1 };
493                    assert!(
494                        F >= 0 && F < MAX_ONE_F,
495                        "`Q::ONE` is only available when 1 is exactly representable"
496                    );
497                }
498                Self::new($wrap(1 << F as usize))
499            };
500        }
501
502        impl<const F: i8> One for Q<$t, $a, F> {
503            fn one() -> Self {
504                const {
505                    const MAX_ONE_F: i8 =
506                        <$inner>::BITS as i8 - if <$inner>::MIN == 0 { 0 } else { 1 };
507                    assert!(
508                        F >= 0 && F < MAX_ONE_F,
509                        "`Q::one()` is only available when 1 is exactly representable"
510                    );
511                }
512                Self::ONE
513            }
514        }
515
516        /// T*Q -> T
517        impl<const F: i8> Mul<Q<$t, $a, F>> for $t {
518            type Output = $t;
519
520            #[inline]
521            fn mul(self, rhs: Q<$t, $a, F>) -> Self::Output {
522                rhs.apply(self)
523            }
524        }
525
526        /// T/Q -> T
527        impl<const F: i8> Div<Q<$t, $a, F>> for $t {
528            type Output = $t;
529
530            #[inline]
531            fn div(self, rhs: Q<$t, $a, F>) -> Self::Output {
532                if F > 0 {
533                    <$t>::down(self.up().shs(F) / rhs.inner.up())
534                } else {
535                    self.shsc::<F>() / rhs.inner
536                }
537            }
538        }
539    };
540}
541// Signed
542impl_q!(Q8<i8, i16>);
543impl_q!(Q16<i16, i32>);
544impl_q!(Q32<i32, i64>);
545impl_q!(Q64<i64, i128>);
546// Unsigned (_P_ositive)
547impl_q!(P8<u8, u16>);
548impl_q!(P16<u16, u32>);
549impl_q!(P32<u32, u64>);
550impl_q!(P64<u64, u128>);
551// _W_rapping signed
552impl_q!(W8<i8, i16>, Wrapping);
553impl_q!(W16<i16, i32>, Wrapping);
554impl_q!(W32<i32, i64>, Wrapping);
555impl_q!(W64<i64, i128>, Wrapping);
556// Wrapping vnsigned
557impl_q!(V8<u8, u16>, Wrapping);
558impl_q!(V16<u16, u32>, Wrapping);
559impl_q!(V32<u32, u64>, Wrapping);
560impl_q!(V64<u64, u128>, Wrapping);
561
562// NonZero<T>, Saturating<T> don't implement Shr/Shl
563
564#[cfg(test)]
565mod test {
566    use super::*;
567    use num_traits::{Bounded, FromPrimitive, Signed, ToPrimitive};
568
569    #[test]
570    fn ratio() {
571        let third = Q32::<28>::from_ratio(1i32, 3);
572        assert!((third.as_f64() - 1.0 / 3.0).abs() < Q32::<28>::DELTA as f64);
573        assert_eq!(Q32::<8>::from_ratio(-3, 2), Q32::from_bits(-384));
574
575        let reciprocal = 3.0f32.recip();
576        assert_eq!(
577            f32::from_ratios([1.0, 2.0], 3.0),
578            [reciprocal, 2.0 * reciprocal]
579        );
580        assert_eq!(
581            Q32::<8>::from_ratios([-3, 3], 2),
582            [Q32::from_bits(-384), Q32::from_bits(384)]
583        );
584    }
585
586    #[test]
587    fn simple() {
588        assert_eq!(
589            Q32::<5>::from_int(4) * Q32::<5>::from_int(3),
590            Q32::from_int(3 * 4)
591        );
592        assert_eq!(
593            Q32::<5>::from_int(12) / Q32::<5>::from_int(6),
594            Q32::from_int(2)
595        );
596        assert_eq!(7 * Q32::<4>::from_bits(0x33), 7 * 3 + ((3 * 7) >> 4));
597        assert_eq!(Q32::<4>::from_bits(0x33).apply(7), 7 * 3 + ((3 * 7) >> 4));
598        assert_eq!(
599            Q32::<4>::from_bits(0x33).mul_wide(7).quantize(),
600            7 * Q32::<4>::from_bits(0x33)
601        );
602    }
603
604    #[test]
605    fn numeric_traits() {
606        assert_eq!(Q8::<4>::min_value().into_bits(), i8::MIN);
607        assert_eq!(Q8::<4>::max_value().into_bits(), i8::MAX);
608        assert_eq!(Q8::<4>::from_f32(1.5).to_i32(), Some(1));
609        assert_eq!(Q8::<4>::from_f32(1.5).to_f64(), Some(1.5));
610        assert_eq!(Q8::<4>::from_i32(3), Some(Q8::<4>::from_int(3)));
611        assert_eq!(Q8::<4>::from_f32(-1.5).abs(), Q8::<4>::from_f32(1.5));
612        assert_eq!(Q8::<4>::from_f32(-1.5).signum(), Q8::<4>::from_int(-1));
613        assert!(Q8::<4>::from_f32(-1.5).is_negative());
614    }
615
616    #[cfg(feature = "bytemuck")]
617    #[test]
618    fn bytemuck_traits() {
619        use bytemuck::TransparentWrapper;
620
621        let q = Q8::<4>::from_int(3);
622        assert_eq!(q.into_bits(), 48);
623        assert_eq!(Q8::<4>::wrap(48i8), q);
624        assert_eq!(*Q8::<4>::wrap_ref(&48i8), q);
625    }
626}