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
10//!
11//! A high-performance fixed-point trigonometry library optimized for embedded systems and microcontrollers
12//! without a floating-point unit (FPU).
13//!
14//! ## Overview
15//!
16//! Fixed-Math-Taylor eliminates the overhead of software floating-point emulation on resource-constrained
17//! devices by implementing trigonometric functions using only integer arithmetic and bit shifts. Designed
18//! specifically for the RP2040 and similar microcontrollers, it provides multiple calculation engines
19//! with different precision/speed trade-offs.
20//!
21//! ## Features & Calculation Engines
22//!
23//! The library provides three distinct calculation engines, selectable via Cargo features:
24//!
25//! - **`lut`** (default): Lookup table with linear interpolation. Ultra-fast, highest precision (~0.1% error),
26//!   640 bytes Flash. Ideal for control loops, motor control, and audio applications.
27//! - **`taylor`**: Taylor series expansion (order 5). Pure algorithmic computation with high precision.
28//!   Smaller memory footprint, better for algorithms-only use cases.
29//! - **`fast-sin`**: Bhaskara I approximation. Medium precision with exceptional speed. Optimal for
30//!   animations and graphics.
31//!
32//! ## Number Format: Q15 Fixed-Point
33//!
34//! All functions return results in **Q15 format** (16-bit signed integer):
35//! - `32767` represents `+1.0`
36//! - `0` represents `0.0`
37//! - `-32768` represents approximately `-1.0`
38//!
39//! To convert to a human-readable float:
40//! ```rust,ignore
41//! let fixed_value: i16 = /* ... */;
42//! let float_value: f32 = (fixed_value as f32) / 32767.0;
43//! ```
44//!
45//! ## Angle Representation
46//!
47//! Angles are represented as `u16` values (0..=65535):
48//! - `0` = 0 radians
49//! - `16384` ≈ π/2
50//! - `32768` ≈ π
51//! - `65535` ≈ 2π
52//!
53//! ## Quick Start
54//!
55//! Add to your `Cargo.toml`:
56//!
57//! ```toml
58//! [dependencies]
59//! fixed-math-taylor = { version = "0.3", features = ["lut"] }
60//! ```
61//!
62//! Basic usage:
63//!
64//! ```rust,ignore
65//! use fixed_math_taylor::{sin_fixed, cos_fixed, Angle, Fixed};
66//!
67//! let angle: Angle = 16384; // π/2
68//! let sine_value: Fixed = sin_fixed(angle);
69//! let cosine_value: Fixed = cos_fixed(angle);
70//!
71//! // Convert to float for display
72//! println!("sin(π/2) ≈ {}", (sine_value as f32) / 32767.0);
73//! ```
74//!
75//! ## Performance Characteristics
76//!
77//! | Engine    | Speed         | Precision  | Flash Cost | Best For                    |
78//! |-----------|---------------|------------|------------|-----------------------------|
79//! | LUT       | Fastest       | ~0.1% err  | 640 bytes  | Control loops, motor ctrl   |
80//! | Taylor    | Fast          | High       | ~500 bytes | Pure algorithms             |
81//! | Fast-Sin  | Fastest       | Medium     | ~300 bytes | Graphics, animations        |
82//!
83//! ## no_std Environment
84//!
85//! This crate is `#![no_std]` compatible and suitable for embedded environments with no standard library.
86//! No dynamic allocations are performed.
87//!
88//! ## Dependencies
89//!
90//! Zero external dependencies. Pure Rust with platform-independent implementation.
91
92// --- TYPES DE BASE ---
93
94/// An angle in the range [0, 65535], representing a full rotation [0, 2π).
95///
96/// The mapping is:
97/// - `0` → 0 radians
98/// - `16384` → π/2
99/// - `32768` → π
100/// - `49152` → 3π/2
101/// - `65535` → ~2π
102///
103/// This representation allows fast angle arithmetic using only bit shifts
104/// and avoids floating-point operations entirely.
105pub type Angle = u16;
106
107/// A fixed-point number in Q15 format, suitable for storing results from trigonometric functions.
108///
109/// Q15 maps the range [-32768, 32767] to the mathematical range [-1.0, 1.0):
110/// - `32767` represents `+1.0`
111/// - `0` represents `0.0`
112/// - `-32768` represents approximately `-1.0`
113///
114/// To convert to a float: `(fixed_value as f32) / 32767.0`
115///
116/// This format is native to 16-bit integer arithmetic and matches the output
117/// of all trigonometric functions in this library.
118pub type Fixed = i16;
119
120// ==========================================
121// MOTEUR LUT (FEATURE "lut")
122// ==========================================
123#[cfg(feature = "lut")]
124mod lut_impl {
125    use super::{Angle, Fixed};
126    const QUADRANT_BITS: u32 = 14;
127    const LUT_SIZE: usize = 256;
128    const LUT_BITS: u32 = 8;
129    const LUT_MASK: u32 = (1 << (QUADRANT_BITS - LUT_BITS)) - 1;
130
131    // Inclusion de la table de sinus (0 à PI/2)
132    static SIN_LUT: [Fixed; 257] = include!("sin_table.rs.inc");
133
134    #[inline(always)]
135    pub fn sin_fixed(angle: Angle) -> Fixed {
136        let quadrant = (angle >> QUADRANT_BITS) as usize;
137        let idx = (angle & 0x3FFF) as u32;
138        let lut_idx = (idx >> (QUADRANT_BITS - LUT_BITS)) as usize;
139        let frac = (idx & LUT_MASK) as i32;
140
141        match quadrant {
142            0 => interpolate(SIN_LUT[lut_idx], SIN_LUT[lut_idx + 1], frac),
143            1 => interpolate(
144                SIN_LUT[LUT_SIZE - lut_idx],
145                SIN_LUT[LUT_SIZE - lut_idx - 1],
146                frac,
147            ),
148            2 => -interpolate(SIN_LUT[lut_idx], SIN_LUT[lut_idx + 1], frac),
149            _ => -interpolate(
150                SIN_LUT[LUT_SIZE - lut_idx],
151                SIN_LUT[LUT_SIZE - lut_idx - 1],
152                frac,
153            ),
154        }
155    }
156
157    #[inline(always)]
158    fn interpolate(y0: Fixed, y1: Fixed, frac: i32) -> Fixed {
159        let y0_32 = y0 as i32;
160        let y1_32 = y1 as i32;
161        (y0_32 + (((y1_32 - y0_32) * frac) >> (QUADRANT_BITS - LUT_BITS))) as Fixed
162    }
163}
164
165// Ré-exportation et fonctions publiques liées à la LUT
166
167/// Computes the sine of an angle using the lookup table engine.
168///
169/// This function provides the fastest and most memory-efficient trigonometric
170/// computation with high accuracy (~0.1% error).
171///
172/// # Input
173/// - `angle`: An [`Angle`] value in the range [0, 65535] representing [0, 2π).
174///
175/// # Output
176/// A [`Fixed`] value in Q15 format, where 32767 ≈ 1.0 and -32768 ≈ -1.0.
177///
178/// # Algorithm
179/// Uses a 257-entry lookup table with linear interpolation. Exploits trigonometric
180/// symmetry to store only the first quadrant while computing all four quadrants.
181///
182/// # Example
183/// ```ignore
184/// use fixed_math_taylor::sin_fixed;
185///
186/// // π/2 in angle representation
187/// let angle = 16384u16;
188/// let result = sin_fixed(angle);
189/// // result ≈ 32767 (representing 1.0)
190/// ```
191///
192/// # Performance
193/// ~2.4 µs on RP2040 at 125 MHz
194#[cfg(feature = "lut")]
195pub use lut_impl::sin_fixed;
196
197/// Computes the cosine of an angle using the lookup table engine.
198///
199/// Equivalent to `sin(angle + π/2)`. Provides the same performance and accuracy
200/// as [`sin_fixed`].
201///
202/// # Input
203/// - `angle`: An [`Angle`] value in the range [0, 65535] representing [0, 2π).
204///
205/// # Output
206/// A [`Fixed`] value in Q15 format, where 32767 ≈ 1.0 and -32768 ≈ -1.0.
207///
208/// # Example
209/// ```ignore
210/// use fixed_math_taylor::cos_fixed;
211///
212/// // π in angle representation
213/// let angle = 32768u16;
214/// let result = cos_fixed(angle);
215/// // result ≈ -32768 (representing -1.0)
216/// ```
217///
218/// # Performance
219/// ~2.4 µs on RP2040 at 125 MHz
220#[cfg(feature = "lut")]
221#[inline(always)]
222pub fn cos_fixed(angle: Angle) -> Fixed {
223    sin_fixed(angle.wrapping_add(16384))
224}
225
226/// Computes both sine and cosine of an angle simultaneously.
227///
228/// More efficient than calling [`sin_fixed`] and [`cos_fixed`] separately,
229/// as some intermediate calculations are shared.
230///
231/// # Input
232/// - `angle`: An [`Angle`] value in the range [0, 65535] representing [0, 2π).
233///
234/// # Output
235/// A tuple `(sin_value, cos_value)`, both in Q15 [`Fixed`] format.
236///
237/// # Example
238/// ```ignore
239/// use fixed_math_taylor::sin_cos;
240///
241/// let angle = 16384u16;
242/// let (sin_val, cos_val) = sin_cos(angle);
243/// ```
244///
245/// # Performance
246/// ~3.5 µs on RP2040 at 125 MHz (faster than two separate calls)
247#[cfg(feature = "lut")]
248#[inline(always)]
249pub fn sin_cos(angle: Angle) -> (Fixed, Fixed) {
250    (sin_fixed(angle), cos_fixed(angle))
251}
252// ==========================================
253// MOTEUR TAYLOR (Q15 - 100% Entiers)
254// ==========================================
255
256/// Pure algorithmic sine and cosine using 5th-order Taylor series expansion.
257///
258/// This module provides trigonometric functions without requiring a lookup table,
259/// making it ideal for scenarios where Flash memory is constrained or where
260/// purely algorithmic computation is preferred.
261///
262/// Both functions use 100% integer arithmetic (Q15 fixed-point), with intermediate
263/// 64-bit calculations to prevent overflow.
264#[cfg(feature = "taylor")]
265pub mod taylor_impl {
266    use super::{Angle, Fixed};
267
268    /// Computes sine using 5th-order Taylor series expansion (Q15).
269    ///
270    /// # Performance
271    /// ~12.8 µs on RP2040 at 125 MHz
272    ///
273    /// # Precision
274    /// High accuracy (~0.01% error) with pure integer arithmetic.
275    ///
276    /// # Example
277    /// ```ignore
278    /// use fixed_math_taylor::taylor_impl;
279    ///
280    /// let angle = 16384u16; // π/2
281    /// let result = taylor_impl::sin_taylor(angle);
282    /// // result ≈ 32767 (representing 1.0)
283    /// ```
284    pub fn sin_taylor(angle: Angle) -> Fixed {
285        let x_input = if angle > 32768 {
286            65536 - angle as i32
287        } else {
288            angle as i32
289        };
290        let x = if x_input > 16384 {
291            32768 - x_input
292        } else {
293            x_input
294        };
295
296        let x_rad = (x * 51472) >> 14;
297
298        let x2 = (x_rad * x_rad) >> 15;
299        let x3 = (x2 * x_rad) >> 15;
300        let x5 = (((x3 * x2) >> 15) * x2) >> 15;
301
302        let term3 = (x3 * 5461) >> 15;
303        let term5 = (x5 * 273) >> 15;
304
305        // C'EST CETTE LIGNE QUI DOIT ÊTRE ICI :
306        let res = (x_rad - term3 + term5) as Fixed;
307
308        if angle > 32768 {
309            -res
310        } else {
311            res
312        }
313    }
314
315    /// Computes cosine using the identity cos(x) = sin(x + π/2).
316    ///
317    /// # Performance
318    /// ~12.8 µs on RP2040 at 125 MHz (same as sin_taylor)
319    pub fn cos_taylor(angle: super::Angle) -> super::Fixed {
320        sin_taylor(angle.wrapping_add(16384))
321    }
322}
323
324// ==========================================
325// MOTEUR FAST (Bhaskara I Q15)
326// ==========================================
327
328/// Ultra-fast approximation using Bhaskara I formula.
329///
330/// This module provides the speediest trigonometric computation at the cost
331/// of slightly lower precision. Ideal for graphics, animations, and scenarios
332/// where speed is critical.
333///
334/// Uses the Bhaskara I approximation formula in pure Q15 integer arithmetic.
335#[cfg(feature = "fast-sin")]
336pub mod fast_impl {
337    use super::{Angle, Fixed};
338
339    /// Computes sine using Bhaskara I approximation (Q15).
340    ///
341    /// # Performance
342    /// ~1.6 µs on RP2040 at 125 MHz (fastest option)
343    ///
344    /// # Precision
345    /// Medium accuracy (~0.5% error). Suitable for graphics and animations.
346    ///
347    /// # Example
348    /// ```ignore
349    /// use fixed_math_taylor::fast_impl;
350    ///
351    /// let angle = 16384u16; // π/2
352    /// let result = fast_impl::sin_fast(angle);
353    /// // result ≈ 32767 (representing 1.0)
354    /// ```
355    pub fn sin_fast(angle: Angle) -> Fixed {
356        // 0..PI (0..32768)
357        let x = (angle & 0x7FFF) as i32;
358        let pi = 32768i32;
359
360        // num = 4x(pi-x)
361        let x_pi_x = (x * (pi - x)) >> 15; // Reste en Q15
362
363        // Formule de Bhaskara simplifiée pour calcul entier :
364        // sin(x) ≈ (16x(pi-x)) / (5pi^2 - 4x(pi-x))
365        let num = (x_pi_x as i64) * 16;
366        let den = (5 * 32768) - (4 * x_pi_x); // Approximation du dénominateur
367
368        // On scale le numérateur pour la division Q15
369        let res = (num * 32767) / den as i64;
370
371        let val = res as Fixed;
372        if angle > 32768 {
373            -val
374        } else {
375            val
376        }
377    }
378
379    /// Computes cosine using the identity cos(x) = sin(x + π/2).
380    ///
381    /// # Performance
382    /// ~1.6 µs on RP2040 at 125 MHz (same as sin_fast)
383    pub fn cos_fast(angle: super::Angle) -> super::Fixed {
384        sin_fast(angle.wrapping_add(16384))
385    }
386}
387
388// ==========================================
389// UTILITAIRES COMMUNS
390// ==========================================
391
392/// Converts a float value to Q15 fixed-point format.
393///
394/// # Input
395/// - `x`: A floating-point value in the range [-1.0, 1.0]
396///
397/// # Output
398/// A [`Fixed`] value representing the input in Q15 format.
399///
400/// # Example
401/// ```ignore
402/// use fixed_math_taylor::to_fixed;
403///
404/// let f = to_fixed(0.5);
405/// assert_eq!(f, 16384); // 0.5 * 32767 ≈ 16384
406/// ```
407#[inline(always)]
408pub fn to_fixed(x: f32) -> Fixed {
409    (x * 32767.0) as Fixed
410}
411
412/// Converts a Q15 fixed-point value to a float.
413///
414/// # Input
415/// - `x`: A [`Fixed`] value in Q15 format
416///
417/// # Output
418/// A floating-point value representing the input (approximately in [-1.0, 1.0])
419///
420/// # Example
421/// ```ignore
422/// use fixed_math_taylor::from_fixed;
423///
424/// let f = from_fixed(32767);
425/// assert!((f - 1.0).abs() < 0.001); // Close to 1.0
426/// ```
427#[inline(always)]
428pub fn from_fixed(x: Fixed) -> f32 {
429    (x as f32) / 32767.0
430}
431
432/// Converts an angle in radians to the [`Angle`] representation used by this library.
433///
434/// # Input
435/// - `rads`: An angle in radians. Values outside [0, 2π) wrap around automatically.
436///
437/// # Output
438/// An [`Angle`] value suitable for the trigonometric functions.
439///
440/// # Example
441/// ```ignore
442/// use fixed_math_taylor::radians_to_angle;
443/// use core::f32::consts::PI;
444///
445/// let angle = radians_to_angle(PI / 2.0);
446/// assert_eq!(angle, 16384); // π/2
447/// ```
448#[inline(always)]
449pub fn radians_to_angle(rads: f32) -> Angle {
450    let scale = 65536.0 / (2.0 * core::f32::consts::PI);
451    (rads * scale) as i32 as u16
452}
453
454// ==========================================
455// TESTS UNITAIRES
456// ==========================================
457#[cfg(test)]
458mod tests {
459    extern crate std;
460    use super::*;
461    use core::f32::consts::PI;
462
463    #[cfg(feature = "lut")]
464    #[test]
465    fn test_sin_fixed_precision() {
466        // Points cardinaux : Précision exacte (tolérance 1 bit)
467        assert!((sin_fixed(0) - 0).abs() <= 1);
468        assert!((sin_fixed(16384) - 32767).abs() <= 1); // PI/2 (1.0)
469        assert!((sin_fixed(32768) - 0).abs() <= 1); // PI (0.0)
470        assert!((sin_fixed(49152) - (-32767)).abs() <= 1); // 3PI/2 (-1.0)
471
472        // Test à 45°
473        let res_raw = sin_fixed(8192);
474        let expected_raw = 23203;
475        assert_eq!(res_raw, expected_raw, "Erreur de précision à 45°");
476    }
477
478    #[cfg(feature = "lut")]
479    #[test]
480    fn test_cos_fixed() {
481        // CORRECTION : Appel de cos_fixed au lieu de cos
482        assert!((cos_fixed(0) - 32767).abs() <= 1);
483        assert!(cos_fixed(16384).abs() <= 1);
484        assert!((cos_fixed(32768) - (-32767)).abs() <= 1);
485    }
486
487    #[cfg(feature = "taylor")]
488    #[test]
489    fn test_taylor_accuracy() {
490        let res = taylor_impl::sin_taylor(8192); // 45°
491        let expected = 23170;
492        assert!((res - expected).abs() < 1000);
493    }
494
495    #[cfg(feature = "fast-sin")]
496    #[test]
497    fn test_fast_sin_approximation() {
498        let res = fast_impl::sin_fast(5461); // 30°
499        let expected = 16384;
500        assert!((res - expected).abs() < 1500);
501    }
502
503    #[test]
504    fn test_radians_to_angle_wrapping() {
505        assert_eq!(radians_to_angle(0.0), 0);
506        assert_eq!(radians_to_angle(2.0 * PI), 0);
507        let a = radians_to_angle(-PI / 2.0);
508        assert!(a == 49152 || a == 49151);
509    }
510
511    #[test]
512    fn test_fixed_conversion_roundtrip() {
513        let original = 0.5f32;
514        let fixed = to_fixed(original);
515        let back = from_fixed(fixed);
516        assert!((original - back).abs() < 0.0001);
517    }
518
519    #[test]
520    fn test_sin_cos_simultaneous() {
521        #[cfg(feature = "lut")]
522        {
523            let (s, c) = sin_cos(0);
524            assert_eq!(s, 0);
525            assert_eq!(c, 32767);
526        }
527    }
528
529    #[test]
530    fn test_cos_consistency() {
531        let _angle_45 = 8192;
532
533        #[cfg(feature = "lut")]
534        assert!((cos_fixed(0) - 32767).abs() <= 1);
535
536        #[cfg(feature = "taylor")]
537        {
538            // CORRECTION : S'assure que cos_taylor est bien appelé
539            let res = taylor_impl::cos_taylor(angle_45);
540            assert!((res - 23170).abs() < 1000);
541        }
542
543        #[cfg(feature = "fast-sin")]
544        {
545            // CORRECTION : S'assure que cos_fast est bien appelé
546            let res = fast_impl::cos_fast(0);
547            assert!((res - 32767).abs() < 1500);
548        }
549    }
550}