Skip to main content

fixed_math_taylor/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2// This program is free software: you can redistribute it and/or modify
3// it under the terms of the GNU General Public License as published by
4// the Free Software Foundation, either version 2 or the License, or
5// (at your option) any later version.
6
7#![no_std]
8
9//! # Fixed-Math-Taylor (Modular Edition)
10//! 
11//! Bibliothèque de trigonométrie haute performance.
12//! Activez les moteurs souhaités via les Cargo Features :
13//! - `lut` : Virgule fixe Q15 ultra-rapide (recommandé pour MCU).
14//! - `taylor` : Série de Taylor (f32) pour la précision.
15//! - `fast-sin` : Approximation de Bhaskara I (f32) pour la vitesse.
16
17// --- TYPES DE BASE ---
18pub type Angle = u16; // 0..65535 = 0..2π
19pub type Fixed = i16; // Q15
20
21// ==========================================
22// MOTEUR LUT (FEATURE "lut")
23// ==========================================
24#[cfg(feature = "lut")]
25mod lut_impl {
26    use super::{Angle, Fixed};
27    const QUADRANT_BITS: u32 = 14;
28    const LUT_SIZE: usize = 256;
29    const LUT_BITS: u32 = 8;
30    const LUT_MASK: u32 = (1 << (QUADRANT_BITS - LUT_BITS)) - 1;
31
32    // Inclusion de la table de sinus (0 à PI/2)
33    static SIN_LUT: [Fixed; 257] = include!("sin_table.rs.inc");
34
35    #[inline(always)]
36    pub fn sin_fixed(angle: Angle) -> Fixed {
37        let quadrant = (angle >> QUADRANT_BITS) as usize;
38        let idx = (angle & 0x3FFF) as u32;
39        let lut_idx = (idx >> (QUADRANT_BITS - LUT_BITS)) as usize;
40        let frac = (idx & LUT_MASK) as i32;
41
42        match quadrant {
43            0 => interpolate(SIN_LUT[lut_idx], SIN_LUT[lut_idx + 1], frac),
44            1 => interpolate(SIN_LUT[LUT_SIZE - lut_idx], SIN_LUT[LUT_SIZE - lut_idx - 1], frac),
45            2 => -interpolate(SIN_LUT[lut_idx], SIN_LUT[lut_idx + 1], frac),
46            _ => -interpolate(SIN_LUT[LUT_SIZE - lut_idx], SIN_LUT[LUT_SIZE - lut_idx - 1], frac),
47        }
48    }
49
50    #[inline(always)]
51    fn interpolate(y0: Fixed, y1: Fixed, frac: i32) -> Fixed {
52        let y0_32 = y0 as i32;
53        let y1_32 = y1 as i32;
54        (y0_32 + (((y1_32 - y0_32) * frac) >> (QUADRANT_BITS - LUT_BITS))) as Fixed
55    }
56}
57
58// Ré-exportation et fonctions publiques liées à la LUT
59#[cfg(feature = "lut")]
60pub use lut_impl::sin_fixed;
61
62#[cfg(feature = "lut")]
63#[inline(always)]
64pub fn cos(angle: Angle) -> Fixed {
65    sin_fixed(angle.wrapping_add(16384))
66}
67
68#[cfg(feature = "lut")]
69#[inline(always)]
70pub fn sin_cos(angle: Angle) -> (Fixed, Fixed) {
71    (sin_fixed(angle), cos(angle))
72}
73
74// ==========================================
75// MOTEUR TAYLOR (FEATURE "taylor")
76// ==========================================
77#[cfg(feature = "taylor")]
78pub mod taylor_impl {
79    pub fn sin_taylor(x: f32) -> f32 {
80        let x2 = x * x;
81        // Approximation d'ordre 9 via méthode de Horner
82        x * (1.0 + x2 * (-0.166666666 + x2 * (0.008333333 + x2 * (-0.000198412 + x2 * 0.000002755))))
83    }
84}
85
86// ==========================================
87// MOTEUR FAST (FEATURE "fast-sin")
88// ==========================================
89#[cfg(feature = "fast-sin")]
90pub mod fast_impl {
91    use core::f32::consts::PI;
92    pub fn sin_fast(x: f32) -> f32 {
93        let num = 16.0 * x * (PI - x.abs());
94        let den = 5.0 * PI * PI - 4.0 * x * (PI - x.abs());
95        num / den
96    }
97}
98
99// ==========================================
100// UTILITAIRES COMMUNS
101// ==========================================
102
103#[inline(always)]
104pub fn to_fixed(x: f32) -> Fixed { (x * 32767.0) as Fixed }
105
106#[inline(always)]
107pub fn from_fixed(x: Fixed) -> f32 { (x as f32) / 32767.0 }
108
109#[inline(always)]
110pub fn radians_to_angle(rads: f32) -> Angle {
111    let scale = 65536.0 / (2.0 * core::f32::consts::PI);
112    (rads * scale) as i32 as u16
113}
114
115// ==========================================
116// TESTS UNITAIRES
117// ==========================================
118#[cfg(test)]
119mod tests {
120    extern crate std;
121    use super::*;
122    use core::f32::consts::PI;
123
124   #[cfg(feature = "lut")]
125    #[test]
126    fn test_sin_fixed_precision() {
127        // Points cardinaux : Précision exacte (tolérance 1 bit)
128        assert!((sin_fixed(0) - 0).abs() <= 1);
129        assert!((sin_fixed(16384) - 32767).abs() <= 1); // PI/2 (1.0)
130        assert!((sin_fixed(32768) - 0).abs() <= 1);     // PI (0.0)
131        assert!((sin_fixed(49152) - (-32767)).abs() <= 1); // 3PI/2 (-1.0)
132
133        // Test à 45° (Angle 8192 = Index 128 dans une table de 256 pts)
134        // Dans ta table, SIN_LUT[128] est exactement 23203.
135        let res_raw = sin_fixed(8192); 
136        let expected_raw = 23203; 
137        
138        assert_eq!(res_raw, expected_raw, "Erreur de précision à 45°");
139    }
140
141    #[cfg(feature = "lut")]
142    #[test]
143    fn test_cos_fixed() {
144        assert!((cos(0) - 32767).abs() <= 1);
145        assert!(cos(16384).abs() <= 1);
146        assert!((cos(32768) - (-32767)).abs() <= 1);
147    }
148
149    #[cfg(feature = "taylor")]
150    #[test]
151    fn test_taylor_accuracy() {
152        let test_val = PI / 4.0;
153        let res = taylor_impl::sin_taylor(test_val);
154        let expected = test_val.sin();
155        assert!((res - expected).abs() < 0.0001);
156    }
157
158    #[cfg(feature = "fast-sin")]
159    #[test]
160    fn test_fast_sin_approximation() {
161        let res = fast_impl::sin_fast(PI / 6.0);
162        assert!((res - 0.5).abs() < 0.005);
163    }
164
165    #[test]
166    fn test_radians_to_angle_wrapping() {
167        assert_eq!(radians_to_angle(0.0), 0);
168        assert_eq!(radians_to_angle(2.0 * PI), 0);
169        // Utilisation de la tolérance pour f32
170        let a = radians_to_angle(-PI / 2.0);
171        assert!(a == 49152 || a == 49151); 
172    }
173
174    #[test]
175    fn test_fixed_conversion_roundtrip() {
176        let original = 0.5f32;
177        let fixed = to_fixed(original);
178        let back = from_fixed(fixed);
179        assert!((original - back).abs() < 0.0001);
180    }
181
182    #[test]
183    fn test_sin_cos_simultaneous() {
184        #[cfg(feature = "lut")]
185        {
186            let (s, c) = sin_cos(0);
187            assert_eq!(s, 0);
188            assert_eq!(c, 32767);
189        }
190    }
191}