Skip to main content

scirs2_core/fixed_point/
mod.rs

1//! Fixed-Point Arithmetic for Embedded Systems
2//!
3//! This module provides fixed-point number types and operations for embedded systems
4//! without hardware floating-point units (FPU). Fixed-point arithmetic offers:
5//!
6//! - **Deterministic Performance**: Constant-time operations
7//! - **No FPU Required**: Works on ARM Cortex-M0/M3, AVR, etc.
8//! - **Predictable Precision**: Known precision at compile-time
9//! - **Efficient**: Uses integer operations
10//!
11//! # Fixed-Point Format
12//!
13//! Fixed-point numbers use Q notation: Qm.n format where:
14//! - m = integer bits
15//! - n = fractional bits
16//!
17//! Common formats:
18//! - **Q15.16**: 16-bit integer, 16-bit fraction (i32 storage)
19//! - **Q7.24**: 8-bit integer, 24-bit fraction (i32 storage)
20//! - **Q31.32**: 32-bit integer, 32-bit fraction (i64 storage)
21//!
22//! # Example: Basic Fixed-Point Math
23//!
24//! ```rust
25//! use scirs2_core::fixed_point::*;
26//!
27//! // Q15.16 format (most common for general use)
28//! let a = Fixed32::<16>::from_float(3.14159);
29//! let b = Fixed32::<16>::from_float(2.0);
30//!
31//! let sum = a + b;
32//! let product = a * b;
33//!
34//! let result: f32 = sum.to_float();
35//! assert!((result - 5.14159).abs() < 0.001);
36//! ```
37//!
38//! # Example: Signal Processing
39//!
40//! ```rust
41//! use scirs2_core::fixed_point::*;
42//!
43//! // FIR filter with fixed-point coefficients
44//! let coeffs = [
45//!     Fixed32::<16>::from_float(0.1),
46//!     Fixed32::<16>::from_float(0.2),
47//!     Fixed32::<16>::from_float(0.4),
48//!     Fixed32::<16>::from_float(0.2),
49//!     Fixed32::<16>::from_float(0.1),
50//! ];
51//!
52//! // Apply filter
53//! let mut output = Fixed32::<16>::ZERO;
54//! for coeff in &coeffs {
55//!     output = output + *coeff;
56//! }
57//! ```
58
59#![cfg_attr(not(feature = "std"), no_std)]
60
61#[cfg(feature = "fixed-point")]
62use fixed::{types::*, FixedI32, FixedI64};
63
64use core::fmt;
65use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
66
67/// Fixed-point number with 32-bit storage (Q format)
68///
69/// Generic over the number of fractional bits N.
70/// Common configurations:
71/// - N=16: Q15.16 format (range: ±32768, precision: 0.0000153)
72/// - N=24: Q7.24 format (range: ±128, precision: 0.00000006)
73///
74/// # Example
75///
76/// ```rust
77/// use scirs2_core::fixed_point::Fixed32;
78///
79/// // Q15.16 format
80/// let x = Fixed32::<16>::from_float(3.14159);
81/// let y = Fixed32::<16>::from_int(2);
82/// let z = x * y;
83///
84/// assert!((z.to_float() - 6.28318).abs() < 0.001);
85/// ```
86#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
87pub struct Fixed32<const FRAC: u32> {
88    raw: i32,
89}
90
91impl<const FRAC: u32> Fixed32<FRAC> {
92    /// Zero constant
93    pub const ZERO: Self = Self { raw: 0 };
94
95    /// One constant
96    pub const ONE: Self = Self { raw: 1 << FRAC };
97
98    /// Minimum value
99    pub const MIN: Self = Self { raw: i32::MIN };
100
101    /// Maximum value
102    pub const MAX: Self = Self { raw: i32::MAX };
103
104    /// Create from raw integer representation
105    #[inline]
106    pub const fn from_raw(raw: i32) -> Self {
107        Self { raw }
108    }
109
110    /// Get the raw integer representation
111    #[inline]
112    pub const fn to_raw(self) -> i32 {
113        self.raw
114    }
115
116    /// Create from integer (shifts left by FRAC bits)
117    #[inline]
118    pub const fn from_int(value: i32) -> Self {
119        Self { raw: value << FRAC }
120    }
121
122    /// Convert to integer (truncates fractional part)
123    #[inline]
124    pub const fn to_int(self) -> i32 {
125        self.raw >> FRAC
126    }
127
128    /// Create from float (compile-time if possible)
129    #[inline]
130    pub fn from_float(value: f32) -> Self {
131        let scale = (1u64 << FRAC) as f32;
132        Self {
133            raw: (value * scale) as i32,
134        }
135    }
136
137    /// Convert to float
138    #[inline]
139    pub fn to_float(self) -> f32 {
140        let scale = (1u64 << FRAC) as f32;
141        self.raw as f32 / scale
142    }
143
144    /// Absolute value
145    #[inline]
146    pub const fn abs(self) -> Self {
147        Self {
148            raw: self.raw.abs(),
149        }
150    }
151
152    /// Square root (using integer approximation)
153    ///
154    /// For a fixed-point number in Q format with FRAC fractional bits,
155    /// we compute sqrt by shifting to 64-bit intermediate to maintain precision.
156    /// Given raw value r representing r / 2^FRAC, we want sqrt(r / 2^FRAC) = sqrt(r * 2^FRAC) / 2^FRAC.
157    /// So we compute isqrt(r << FRAC) which gives the raw result directly.
158    pub fn sqrt(self) -> Self {
159        if self.raw <= 0 {
160            return Self::ZERO;
161        }
162
163        // Shift to 64-bit to preserve precision: isqrt(raw << FRAC)
164        let scaled = (self.raw as u64) << FRAC;
165
166        // Newton-Raphson iteration for integer sqrt of scaled value
167        // Initial guess using bit length
168        let bits = 64 - scaled.leading_zeros();
169        let mut result: u64 = 1u64 << ((bits + 1) / 2);
170
171        // Newton iterations (sufficient for 64-bit convergence)
172        for _ in 0..32 {
173            if result == 0 {
174                break;
175            }
176            let next = (result + scaled / result) >> 1;
177            if next >= result {
178                break; // Converged
179            }
180            result = next;
181        }
182
183        Self { raw: result as i32 }
184    }
185
186    /// Saturating addition
187    #[inline]
188    pub const fn saturating_add(self, rhs: Self) -> Self {
189        Self {
190            raw: self.raw.saturating_add(rhs.raw),
191        }
192    }
193
194    /// Saturating subtraction
195    #[inline]
196    pub const fn saturating_sub(self, rhs: Self) -> Self {
197        Self {
198            raw: self.raw.saturating_sub(rhs.raw),
199        }
200    }
201
202    /// Saturating multiplication
203    #[inline]
204    pub fn saturating_mul(self, rhs: Self) -> Self {
205        let result = (self.raw as i64 * rhs.raw as i64) >> FRAC;
206        Self {
207            raw: result.clamp(i32::MIN as i64, i32::MAX as i64) as i32,
208        }
209    }
210}
211
212// Arithmetic operations
213impl<const FRAC: u32> Add for Fixed32<FRAC> {
214    type Output = Self;
215
216    #[inline]
217    fn add(self, rhs: Self) -> Self::Output {
218        Self {
219            raw: self.raw + rhs.raw,
220        }
221    }
222}
223
224impl<const FRAC: u32> AddAssign for Fixed32<FRAC> {
225    #[inline]
226    fn add_assign(&mut self, rhs: Self) {
227        self.raw += rhs.raw;
228    }
229}
230
231impl<const FRAC: u32> Sub for Fixed32<FRAC> {
232    type Output = Self;
233
234    #[inline]
235    fn sub(self, rhs: Self) -> Self::Output {
236        Self {
237            raw: self.raw - rhs.raw,
238        }
239    }
240}
241
242impl<const FRAC: u32> SubAssign for Fixed32<FRAC> {
243    #[inline]
244    fn sub_assign(&mut self, rhs: Self) {
245        self.raw -= rhs.raw;
246    }
247}
248
249impl<const FRAC: u32> Mul for Fixed32<FRAC> {
250    type Output = Self;
251
252    #[inline]
253    fn mul(self, rhs: Self) -> Self::Output {
254        let result = (self.raw as i64 * rhs.raw as i64) >> FRAC;
255        Self { raw: result as i32 }
256    }
257}
258
259impl<const FRAC: u32> MulAssign for Fixed32<FRAC> {
260    #[inline]
261    fn mul_assign(&mut self, rhs: Self) {
262        *self = *self * rhs;
263    }
264}
265
266impl<const FRAC: u32> Div for Fixed32<FRAC> {
267    type Output = Self;
268
269    #[inline]
270    fn div(self, rhs: Self) -> Self::Output {
271        let result = ((self.raw as i64) << FRAC) / rhs.raw as i64;
272        Self { raw: result as i32 }
273    }
274}
275
276impl<const FRAC: u32> DivAssign for Fixed32<FRAC> {
277    #[inline]
278    fn div_assign(&mut self, rhs: Self) {
279        *self = *self / rhs;
280    }
281}
282
283impl<const FRAC: u32> Neg for Fixed32<FRAC> {
284    type Output = Self;
285
286    #[inline]
287    fn neg(self) -> Self::Output {
288        Self { raw: -self.raw }
289    }
290}
291
292impl<const FRAC: u32> Default for Fixed32<FRAC> {
293    fn default() -> Self {
294        Self::ZERO
295    }
296}
297
298impl<const FRAC: u32> fmt::Display for Fixed32<FRAC> {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        write!(f, "{}", self.to_float())
301    }
302}
303
304/// Fixed-point number with 64-bit storage (Q format)
305///
306/// For higher precision or larger range requirements.
307///
308/// # Example
309///
310/// ```rust
311/// use scirs2_core::fixed_point::Fixed64;
312///
313/// // Q31.32 format (high precision)
314/// let x = Fixed64::<32>::from_double(3.14159265358979);
315/// let y = Fixed64::<32>::from_double(2.0);
316/// let z = x * y;
317///
318/// assert!((z.to_double() - 6.28318530717958).abs() < 1e-8);
319/// ```
320#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
321pub struct Fixed64<const FRAC: u32> {
322    raw: i64,
323}
324
325impl<const FRAC: u32> Fixed64<FRAC> {
326    /// Zero constant
327    pub const ZERO: Self = Self { raw: 0 };
328
329    /// One constant
330    pub const ONE: Self = Self { raw: 1 << FRAC };
331
332    /// Create from raw integer representation
333    #[inline]
334    pub const fn from_raw(raw: i64) -> Self {
335        Self { raw }
336    }
337
338    /// Get the raw integer representation
339    #[inline]
340    pub const fn to_raw(self) -> i64 {
341        self.raw
342    }
343
344    /// Create from integer
345    #[inline]
346    pub const fn from_int(value: i64) -> Self {
347        Self { raw: value << FRAC }
348    }
349
350    /// Convert to integer
351    #[inline]
352    pub const fn to_int(self) -> i64 {
353        self.raw >> FRAC
354    }
355
356    /// Create from float
357    #[inline]
358    pub fn from_float(value: f32) -> Self {
359        let scale = (1u64 << FRAC) as f32;
360        Self {
361            raw: (value * scale) as i64,
362        }
363    }
364
365    /// Create from double
366    #[inline]
367    pub fn from_double(value: f64) -> Self {
368        let scale = (1u64 << FRAC) as f64;
369        Self {
370            raw: (value * scale) as i64,
371        }
372    }
373
374    /// Convert to float
375    #[inline]
376    pub fn to_float(self) -> f32 {
377        let scale = (1u64 << FRAC) as f32;
378        self.raw as f32 / scale
379    }
380
381    /// Convert to double
382    #[inline]
383    pub fn to_double(self) -> f64 {
384        let scale = (1u64 << FRAC) as f64;
385        self.raw as f64 / scale
386    }
387}
388
389// Arithmetic operations for Fixed64
390impl<const FRAC: u32> Add for Fixed64<FRAC> {
391    type Output = Self;
392
393    #[inline]
394    fn add(self, rhs: Self) -> Self::Output {
395        Self {
396            raw: self.raw + rhs.raw,
397        }
398    }
399}
400
401impl<const FRAC: u32> Sub for Fixed64<FRAC> {
402    type Output = Self;
403
404    #[inline]
405    fn sub(self, rhs: Self) -> Self::Output {
406        Self {
407            raw: self.raw - rhs.raw,
408        }
409    }
410}
411
412impl<const FRAC: u32> Mul for Fixed64<FRAC> {
413    type Output = Self;
414
415    #[inline]
416    fn mul(self, rhs: Self) -> Self::Output {
417        let result = (self.raw as i128 * rhs.raw as i128) >> FRAC;
418        Self { raw: result as i64 }
419    }
420}
421
422impl<const FRAC: u32> Div for Fixed64<FRAC> {
423    type Output = Self;
424
425    #[inline]
426    fn div(self, rhs: Self) -> Self::Output {
427        let result = ((self.raw as i128) << FRAC) / rhs.raw as i128;
428        Self { raw: result as i64 }
429    }
430}
431
432impl<const FRAC: u32> Default for Fixed64<FRAC> {
433    fn default() -> Self {
434        Self::ZERO
435    }
436}
437
438impl<const FRAC: u32> fmt::Display for Fixed64<FRAC> {
439    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440        write!(f, "{}", self.to_double())
441    }
442}
443
444/// Type aliases for common fixed-point formats
445pub mod types {
446    use super::*;
447
448    /// Q15.16 format - most common for general use
449    /// Range: ±32768, Precision: ~0.000015
450    pub type Q16 = Fixed32<16>;
451
452    /// Q7.24 format - higher precision, smaller range
453    /// Range: ±128, Precision: ~0.00000006
454    pub type Q24 = Fixed32<24>;
455
456    /// Q23.8 format - lower precision, larger range
457    /// Range: ±8388608, Precision: ~0.004
458    pub type Q8 = Fixed32<8>;
459
460    /// Q31.32 format - high precision with 64-bit storage
461    /// Range: ±2147483648, Precision: ~2.3e-10
462    pub type Q32 = Fixed64<32>;
463}
464
465/// Mathematical functions for fixed-point numbers
466pub mod math {
467    use super::*;
468
469    /// Sine approximation using Taylor series (for embedded systems)
470    ///
471    /// Accurate for angles in radians within ±2π
472    pub fn sin<const FRAC: u32>(x: Fixed32<FRAC>) -> Fixed32<FRAC> {
473        // Reduce angle to [-π, π]
474        let pi = Fixed32::<FRAC>::from_float(core::f32::consts::PI);
475        let two_pi = Fixed32::<FRAC>::from_float(2.0 * core::f32::consts::PI);
476
477        let mut angle = x;
478        while angle > pi {
479            angle -= two_pi;
480        }
481        while angle < -pi {
482            angle += two_pi;
483        }
484
485        // Taylor series: sin(x) ≈ x - x³/3! + x⁵/5! - x⁷/7!
486        let x2 = angle * angle;
487        let x3 = x2 * angle;
488        let x5 = x3 * x2;
489        let x7 = x5 * x2;
490
491        let term1 = angle;
492        let term2 = x3 / Fixed32::<FRAC>::from_int(6); // x³/3!
493        let term3 = x5 / Fixed32::<FRAC>::from_int(120); // x⁵/5!
494        let term4 = x7 / Fixed32::<FRAC>::from_int(5040); // x⁷/7!
495
496        term1 - term2 + term3 - term4
497    }
498
499    /// Cosine approximation
500    pub fn cos<const FRAC: u32>(x: Fixed32<FRAC>) -> Fixed32<FRAC> {
501        let half_pi = Fixed32::<FRAC>::from_float(core::f32::consts::FRAC_PI_2);
502        sin(x + half_pi)
503    }
504
505    /// Exponential approximation (e^x)
506    pub fn exp<const FRAC: u32>(x: Fixed32<FRAC>) -> Fixed32<FRAC> {
507        // Series: e^x = 1 + x + x²/2! + x³/3! + x⁴/4! + ...
508        let mut result = Fixed32::<FRAC>::ONE;
509        let mut term = Fixed32::<FRAC>::ONE;
510
511        for n in 1..10 {
512            term = term * x / Fixed32::<FRAC>::from_int(n);
513            result += term;
514
515            // Early exit if term becomes negligible
516            if term.abs().to_raw().abs() < 4 {
517                break;
518            }
519        }
520
521        result
522    }
523
524    /// Natural logarithm approximation
525    pub fn ln<const FRAC: u32>(x: Fixed32<FRAC>) -> Fixed32<FRAC> {
526        if x.to_raw() <= 0 {
527            return Fixed32::<FRAC>::MIN;
528        }
529
530        // Use series expansion around x=1
531        // ln(x) = 2 * ((x-1)/(x+1) + (1/3)*((x-1)/(x+1))³ + ...)
532        let one = Fixed32::<FRAC>::ONE;
533        let numerator = x - one;
534        let denominator = x + one;
535        let y = numerator / denominator;
536        let y2 = y * y;
537
538        let term1 = y;
539        let term2 = (y * y2) / Fixed32::<FRAC>::from_int(3);
540        let term3 = (y * y2 * y2) / Fixed32::<FRAC>::from_int(5);
541
542        Fixed32::<FRAC>::from_int(2) * (term1 + term2 + term3)
543    }
544}
545
546/// Signal processing utilities with fixed-point
547pub mod signal {
548    use super::*;
549
550    /// FIR filter with fixed-point coefficients
551    ///
552    /// # Example
553    ///
554    /// ```rust
555    /// use scirs2_core::fixed_point::{Fixed32, signal::FirFilter};
556    ///
557    /// let coeffs = [Fixed32::<16>::from_float(0.2); 5];
558    /// let mut filter = FirFilter::new(&coeffs);
559    ///
560    /// let output = filter.process(Fixed32::<16>::from_float(1.0));
561    /// ```
562    pub struct FirFilter<const FRAC: u32, const N: usize> {
563        coeffs: [Fixed32<FRAC>; N],
564        buffer: [Fixed32<FRAC>; N],
565        index: usize,
566    }
567
568    impl<const FRAC: u32, const N: usize> FirFilter<FRAC, N> {
569        /// Create a new FIR filter
570        pub fn new(coeffs: &[Fixed32<FRAC>; N]) -> Self {
571            Self {
572                coeffs: *coeffs,
573                buffer: [Fixed32::<FRAC>::ZERO; N],
574                index: 0,
575            }
576        }
577
578        /// Process a single sample
579        pub fn process(&mut self, input: Fixed32<FRAC>) -> Fixed32<FRAC> {
580            // Store input in circular buffer
581            self.buffer[self.index] = input;
582            self.index = (self.index + 1) % N;
583
584            // Convolution
585            let mut output = Fixed32::<FRAC>::ZERO;
586            for i in 0..N {
587                let buf_idx = (self.index + N - 1 - i) % N;
588                output += self.coeffs[i] * self.buffer[buf_idx];
589            }
590
591            output
592        }
593
594        /// Reset the filter state
595        pub fn reset(&mut self) {
596            self.buffer = [Fixed32::<FRAC>::ZERO; N];
597            self.index = 0;
598        }
599    }
600}
601
602#[cfg(test)]
603mod tests {
604    use super::*;
605
606    #[test]
607    fn test_fixed32_basic() {
608        let a = Fixed32::<16>::from_float(core::f32::consts::PI);
609        let b = Fixed32::<16>::from_float(2.0);
610
611        let sum = a + b;
612        assert!((sum.to_float() - (core::f32::consts::PI + 2.0)).abs() < 0.01);
613
614        let product = a * b;
615        assert!((product.to_float() - core::f32::consts::TAU).abs() < 0.01);
616    }
617
618    #[test]
619    fn test_fixed32_sqrt() {
620        let x = Fixed32::<16>::from_float(4.0);
621        let sqrt_x = x.sqrt();
622        assert!((sqrt_x.to_float() - 2.0).abs() < 0.1);
623    }
624
625    #[test]
626    fn test_fixed64_precision() {
627        let a = Fixed64::<32>::from_double(std::f64::consts::PI);
628        let b = Fixed64::<32>::from_double(2.0);
629
630        let product = a * b;
631        assert!((product.to_double() - std::f64::consts::TAU).abs() < 1e-9);
632    }
633
634    #[test]
635    fn test_math_functions() {
636        use math::*;
637
638        let x = Fixed32::<16>::from_float(0.0);
639        let sin_x = sin(x);
640        assert!((sin_x.to_float() - 0.0).abs() < 0.01);
641
642        let half_pi = Fixed32::<16>::from_float(core::f32::consts::FRAC_PI_2);
643        let sin_half_pi = sin(half_pi);
644        assert!((sin_half_pi.to_float() - 1.0).abs() < 0.1);
645    }
646
647    #[test]
648    fn test_fir_filter() {
649        use signal::*;
650
651        let coeffs = [Fixed32::<16>::from_float(0.2); 5];
652        let mut filter = FirFilter::new(&coeffs);
653
654        let input = Fixed32::<16>::from_float(1.0);
655        let output = filter.process(input);
656
657        assert!(output.to_float() > 0.0);
658    }
659}