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_fixed(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_fixed(angle))
72}
73// ==========================================
74// MOTEUR TAYLOR (Q15 - 100% Entiers)
75// ==========================================
76#[cfg(feature = "taylor")]
77pub mod taylor_impl {
78    use super::{Angle, Fixed};
79    
80    pub fn sin_taylor(angle: Angle) -> Fixed {
81        let x_input = if angle > 32768 { 65536 - angle as i32 } else { angle as i32 };
82        let x = if x_input > 16384 { 32768 - x_input } else { x_input };
83
84        let x_rad = (x * 51472) >> 14; 
85
86        let x2 = (x_rad * x_rad) >> 15;
87        let x3 = (x2 * x_rad) >> 15;
88        let x5 = (((x3 * x2) >> 15) * x2) >> 15;
89
90        let term3 = (x3 * 5461) >> 15; 
91        let term5 = (x5 * 273) >> 15;
92
93        // C'EST CETTE LIGNE QUI DOIT ÊTRE ICI :
94        let res = (x_rad - term3 + term5) as Fixed;
95        
96        if angle > 32768 { -res } else { res }
97    }
98
99    pub fn cos_taylor(angle: super::Angle) -> super::Fixed {
100        sin_taylor(angle.wrapping_add(16384))
101    }
102
103    
104
105}
106
107
108// ==========================================
109// MOTEUR FAST (Bhaskara I Q15)
110// ==========================================
111#[cfg(feature = "fast-sin")]
112pub mod fast_impl {
113    use super::{Angle, Fixed};
114
115    pub fn sin_fast(angle: Angle) -> Fixed {
116        // 0..PI (0..32768)
117        let x = (angle & 0x7FFF) as i32; 
118        let pi = 32768i32;
119        
120        // num = 4x(pi-x)
121        let x_pi_x = (x * (pi - x)) >> 15; // Reste en Q15
122        
123        // Formule de Bhaskara simplifiée pour calcul entier :
124        // sin(x) ≈ (16x(pi-x)) / (5pi^2 - 4x(pi-x))
125        let num = (x_pi_x as i64) * 16;
126        let den = (5 * 32768) - ((4 * x_pi_x) >> 0); // Approximation du dénominateur
127        
128        // On scale le numérateur pour la division Q15
129        let res = (num * 32767) / den as i64;
130        
131        let val = res as Fixed;
132        if angle > 32768 { -val } else { val }
133    }
134
135    pub fn cos_fast(angle: super::Angle) -> super::Fixed {
136        sin_fast(angle.wrapping_add(16384))
137    }
138
139 
140}
141
142
143// ==========================================
144// UTILITAIRES COMMUNS
145// ==========================================
146
147#[inline(always)]
148pub fn to_fixed(x: f32) -> Fixed { (x * 32767.0) as Fixed }
149
150#[inline(always)]
151pub fn from_fixed(x: Fixed) -> f32 { (x as f32) / 32767.0 }
152
153#[inline(always)]
154pub fn radians_to_angle(rads: f32) -> Angle {
155    let scale = 65536.0 / (2.0 * core::f32::consts::PI);
156    (rads * scale) as i32 as u16
157}
158
159// ==========================================
160// TESTS UNITAIRES
161// ==========================================
162#[cfg(test)]
163mod tests {
164    extern crate std;
165    use super::*;
166    use core::f32::consts::PI;
167
168    #[cfg(feature = "lut")]
169    #[test]
170    fn test_sin_fixed_precision() {
171        // Points cardinaux : Précision exacte (tolérance 1 bit)
172        assert!((sin_fixed(0) - 0).abs() <= 1);
173        assert!((sin_fixed(16384) - 32767).abs() <= 1); // PI/2 (1.0)
174        assert!((sin_fixed(32768) - 0).abs() <= 1);     // PI (0.0)
175        assert!((sin_fixed(49152) - (-32767)).abs() <= 1); // 3PI/2 (-1.0)
176
177        // Test à 45°
178        let res_raw = sin_fixed(8192); 
179        let expected_raw = 23203; 
180        assert_eq!(res_raw, expected_raw, "Erreur de précision à 45°");
181    }
182
183    #[cfg(feature = "lut")]
184    #[test]
185    fn test_cos_fixed() {
186        // CORRECTION : Appel de cos_fixed au lieu de cos
187        assert!((cos_fixed(0) - 32767).abs() <= 1);
188        assert!(cos_fixed(16384).abs() <= 1);
189        assert!((cos_fixed(32768) - (-32767)).abs() <= 1);
190    }
191
192    #[cfg(feature = "taylor")]
193    #[test]
194    fn test_taylor_accuracy() {
195        let res = taylor_impl::sin_taylor(8192); // 45°
196        let expected = 23170; 
197        assert!((res - expected).abs() < 1000); 
198    }
199
200    #[cfg(feature = "fast-sin")]
201    #[test]
202    fn test_fast_sin_approximation() {
203        let res = fast_impl::sin_fast(5461); // 30°
204        let expected = 16384; 
205        assert!((res - expected).abs() < 1500);
206    }
207
208    #[test]
209    fn test_radians_to_angle_wrapping() {
210        assert_eq!(radians_to_angle(0.0), 0);
211        assert_eq!(radians_to_angle(2.0 * PI), 0);
212        let a = radians_to_angle(-PI / 2.0);
213        assert!(a == 49152 || a == 49151); 
214    }
215
216    #[test]
217    fn test_fixed_conversion_roundtrip() {
218        let original = 0.5f32;
219        let fixed = to_fixed(original);
220        let back = from_fixed(fixed);
221        assert!((original - back).abs() < 0.0001);
222    }
223
224    #[test]
225    fn test_sin_cos_simultaneous() {
226        #[cfg(feature = "lut")]
227        {
228            let (s, c) = sin_cos(0);
229            assert_eq!(s, 0);
230            assert_eq!(c, 32767);
231        }
232    }
233
234    #[test]
235    fn test_cos_consistency() {
236        let angle_45 = 8192; 
237        
238        #[cfg(feature = "lut")]
239        assert!((cos_fixed(0) - 32767).abs() <= 1);
240
241        #[cfg(feature = "taylor")]
242        {
243            // CORRECTION : S'assure que cos_taylor est bien appelé
244            let res = taylor_impl::cos_taylor(angle_45);
245            assert!((res - 23170).abs() < 1000);
246        }
247
248        #[cfg(feature = "fast-sin")]
249        {
250            // CORRECTION : S'assure que cos_fast est bien appelé
251            let res = fast_impl::cos_fast(0);
252            assert!((res - 32767).abs() < 1500);
253        }
254    }
255}