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