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