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}