Skip to main content

embedded_trig_f32/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2// Licence : GNU GPL v2 ou ultérieure
3
4#![no_std]
5#![warn(missing_docs)]
6#![forbid(unsafe_code)]
7
8//! # embedded-trig-f32
9//!
10//! Bibliothèque haute performance pour le calcul trigonométrique en `f32`.
11//! Spécifiquement conçue pour les systèmes embarqués sans bibliothèque standard.
12
13/// Constantes mathématiques pré-calculées.
14pub mod consts {
15    /// La constante PI.
16    pub const PI: f32 = core::f32::consts::PI;
17    /// La constante TAU (2*PI).
18    pub const TAU: f32 = core::f32::consts::TAU;
19    /// La constante PI/2.
20    pub const FRAC_PI_2: f32 = core::f32::consts::FRAC_PI_2;
21}
22
23/// Erreurs de domaine ou de calcul.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum TrigError {
26    /// L'argument dépasse les limites autorisées (ex: asin(1.1)).
27    DomainError,
28    /// Résultat indéfini (ex: division par zéro dans atan2).
29    Undefined,
30    /// Valeur d'entrée non finie (NaN ou Infini).
31    NonFiniteValue,
32}
33
34// ─────────────────────────────────────────────────────────────
35//  Outils de calcul internes
36// ─────────────────────────────────────────────────────────────
37
38#[inline(always)]
39fn internal_sqrt(x: f32) -> f32 {
40    if x <= 0.0 { return 0.0; }
41    #[cfg(all(target_arch = "arm", target_feature = "vfp2"))]
42    { x.sqrt() }
43    #[cfg(not(all(target_arch = "arm", target_feature = "vfp2")))]
44    {
45        let mut r = f32::from_bits(((x.to_bits() >> 1) + 0x1FBB_4F2E) & 0x7FFF_FFFF);
46        r = 0.5 * (r + x / r);
47        r = 0.5 * (r + x / r);
48        r = 0.5 * (r + x / r);
49        r
50    }
51}
52
53#[inline]
54fn wrap_angle(x: f32) -> f32 {
55    let mut w = x % consts::TAU;
56    if w > consts::PI { w -= consts::TAU; }
57    if w <= -consts::PI { w += consts::TAU; }
58    w
59}
60
61/// Approximation sinus très stable (Bhaskara I)
62/// Précision suffisante pour f32 sur l'intervalle [0, PI]
63fn base_sin(x: f32) -> f32 {
64    let x_deg = x * (180.0 / consts::PI);
65    let top = 4.0 * x_deg * (180.0 - x_deg);
66    let bottom = 40500.0 - x_deg * (180.0 - x_deg);
67    top / bottom
68}
69
70// ─────────────────────────────────────────────────────────────
71//  API Publique
72// ─────────────────────────────────────────────────────────────
73
74/// Calcule le sinus.
75pub fn sin(x: f32) -> f32 {
76    if !x.is_finite() { return f32::NAN; }
77    let w = wrap_angle(x);
78    if w < 0.0 {
79        -base_sin(-w)
80    } else {
81        base_sin(w)
82    }
83}
84
85/// Calcule le cosinus.
86pub fn cos(x: f32) -> f32 {
87    sin(x + consts::FRAC_PI_2)
88}
89/// Calcule l'arc tangente (y, x).
90pub fn atan2(y: f32, x: f32) -> Result<f32, TrigError> {
91    if !y.is_finite() || !x.is_finite() { return Err(TrigError::NonFiniteValue); }
92    if x == 0.0 && y == 0.0 { return Err(TrigError::Undefined); }
93
94    let abs_y = y.abs();
95    let abs_x = x.abs();
96    
97    // Approximation rationnelle stable
98    let a = if abs_x > abs_y { abs_y / abs_x } else { abs_x / abs_y };
99    let s = a * a;
100    let mut r = ((-0.0464964749 * s + 0.15931422) * s - 0.327622764) * s * a + a;
101
102    if abs_y > abs_x { r = consts::FRAC_PI_2 - r; }
103    if x < 0.0 { r = consts::PI - r; }
104    if y < 0.0 { r = -r; }
105
106    Ok(r)
107}
108
109/// Calcule l'arc sinus.
110pub fn asin(x: f32) -> Result<f32, TrigError> {
111    if x < -1.0 || x > 1.0 { return Err(TrigError::DomainError); }
112    if x == 1.0 { return Ok(consts::FRAC_PI_2); }
113    if x == -1.0 { return Ok(-consts::FRAC_PI_2); }
114    atan2(x, internal_sqrt(1.0 - x * x))
115}
116
117/// Calcule l'arc cosinus.
118pub fn acos(x: f32) -> Result<f32, TrigError> {
119    if x < -1.0 || x > 1.0 { return Err(TrigError::DomainError); }
120    Ok(consts::FRAC_PI_2 - asin(x)?)
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use core::f32::consts::PI;
127
128    const EPS: f32 = 2e-3; // Bhaskara est stable mais un peu moins précis que Taylor
129
130    #[test]
131    fn test_sin_basic() {
132        assert!((sin(0.0)).abs() < EPS);
133        assert!((sin(PI / 2.0) - 1.0).abs() < EPS);
134        assert!((sin(PI)).abs() < EPS);
135    }
136
137    #[test]
138    fn test_cos_basic() {
139        assert!((cos(0.0) - 1.0).abs() < EPS);
140        assert!((cos(PI / 2.0)).abs() < EPS);
141    }
142
143    #[test]
144    fn test_reduction_angle() {
145        assert!((wrap_angle(3.0 * PI).abs() - PI).abs() < EPS);
146    }
147
148    #[test]
149    fn test_atan2() {
150        assert!((atan2(1.0, 1.0).unwrap() - PI / 4.0).abs() < EPS);
151    }
152
153    #[test]
154    fn test_asin_acos() {
155        assert!((asin(0.5).unwrap() - PI / 6.0).abs() < EPS);
156        assert!((acos(0.5).unwrap() - PI / 3.0).abs() < EPS);
157    }
158}