embedded_charts/math/
mod.rs

1//! Math abstraction layer for no_std compatibility.
2//!
3//! This module provides trait-based math operations that can be backed by different
4//! implementations depending on the target environment and feature flags:
5//!
6//! - `floating-point`: Full floating-point math using micromath
7//! - `libm-math`: Alternative floating-point using libm
8//! - `fixed-point`: Fixed-point arithmetic using the fixed crate
9//! - `integer-math`: Integer-only math for the most constrained environments
10//! - `cordic-math`: CORDIC-based trigonometric functions
11
12pub mod backends;
13pub mod interpolation;
14pub mod traits;
15
16// Re-export the main traits
17pub use traits::{FloatLike, MathOps, TrigOps};
18
19// Re-export backend implementations
20pub use backends::*;
21
22/// Primary numeric type used throughout the library
23#[cfg(feature = "floating-point")]
24pub type Number = f32;
25
26#[cfg(all(feature = "fixed-point", not(feature = "floating-point")))]
27/// Primary numeric type for fixed-point math backend
28pub type Number = fixed::types::I16F16;
29
30#[cfg(all(
31    feature = "integer-math",
32    not(any(feature = "floating-point", feature = "fixed-point"))
33))]
34/// Primary numeric type for integer-only math backend
35pub type Number = i32;
36
37#[cfg(not(any(
38    feature = "floating-point",
39    feature = "fixed-point",
40    feature = "integer-math"
41)))]
42/// Default numeric type for mathematical operations
43pub type Number = f32;
44
45/// Math operations provider - selects the appropriate backend based on features
46pub struct Math;
47
48impl Math {
49    /// Get the appropriate math backend for the current feature configuration
50    #[cfg(feature = "floating-point")]
51    pub fn backend() -> backends::FloatingPointBackend {
52        backends::FloatingPointBackend
53    }
54
55    #[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
56    pub fn backend() -> backends::LibmBackend {
57        backends::LibmBackend
58    }
59
60    #[cfg(all(
61        feature = "fixed-point",
62        not(any(feature = "floating-point", feature = "libm-math"))
63    ))]
64    /// Get the fixed-point math backend instance
65    pub fn backend() -> backends::FixedPointBackend {
66        backends::FixedPointBackend
67    }
68
69    #[cfg(all(
70        feature = "cordic-math",
71        not(any(
72            feature = "floating-point",
73            feature = "libm-math",
74            feature = "fixed-point"
75        ))
76    ))]
77    pub fn backend() -> backends::CordicBackend {
78        backends::CordicBackend
79    }
80
81    #[cfg(all(
82        feature = "integer-math",
83        not(any(
84            feature = "floating-point",
85            feature = "libm-math",
86            feature = "fixed-point",
87            feature = "cordic-math"
88        ))
89    ))]
90    /// Get the integer math backend for constrained environments
91    pub fn backend() -> backends::IntegerBackend {
92        backends::IntegerBackend
93    }
94
95    #[cfg(not(any(
96        feature = "floating-point",
97        feature = "libm-math",
98        feature = "fixed-point",
99        feature = "cordic-math",
100        feature = "integer-math"
101    )))]
102    /// Get the fallback math backend when no specific feature is enabled
103    pub fn backend() -> backends::FallbackBackend {
104        backends::FallbackBackend
105    }
106}
107
108/// Convenience functions for common math operations
109impl Math {
110    /// Calculate the square root of a number
111    #[inline]
112    pub fn sqrt(x: Number) -> Number {
113        use crate::math::traits::MathBackend;
114        Self::backend().sqrt(x)
115    }
116
117    /// Calculate the absolute value of a number
118    #[inline]
119    pub fn abs(x: Number) -> Number {
120        use crate::math::traits::MathBackend;
121        Self::backend().abs(x)
122    }
123
124    /// Calculate the minimum of two numbers
125    #[inline]
126    pub fn min(a: Number, b: Number) -> Number {
127        use crate::math::traits::MathBackend;
128        Self::backend().min(a, b)
129    }
130
131    /// Calculate the maximum of two numbers
132    #[inline]
133    pub fn max(a: Number, b: Number) -> Number {
134        use crate::math::traits::MathBackend;
135        Self::backend().max(a, b)
136    }
137
138    /// Calculate the floor of a number
139    #[inline]
140    pub fn floor(x: Number) -> Number {
141        use crate::math::traits::MathBackend;
142        Self::backend().floor(x)
143    }
144
145    /// Calculate the ceiling of a number
146    #[inline]
147    pub fn ceil(x: Number) -> Number {
148        use crate::math::traits::MathBackend;
149        Self::backend().ceil(x)
150    }
151
152    /// Calculate x raised to the power of y
153    #[inline]
154    pub fn pow(x: Number, y: Number) -> Number {
155        use crate::math::traits::MathBackend;
156        Self::backend().pow(x, y)
157    }
158
159    /// Calculate the natural logarithm
160    #[inline]
161    pub fn ln(x: Number) -> Number {
162        use crate::math::traits::MathBackend;
163        Self::backend().ln(x)
164    }
165
166    /// Calculate the base-10 logarithm
167    #[inline]
168    pub fn log10(x: Number) -> Number {
169        use crate::math::traits::MathBackend;
170        Self::backend().log10(x)
171    }
172
173    /// Calculate the sine of an angle in radians
174    #[inline]
175    pub fn sin(x: Number) -> Number {
176        use crate::math::traits::MathBackend;
177        Self::backend().sin(x)
178    }
179
180    /// Calculate the cosine of an angle in radians
181    #[inline]
182    pub fn cos(x: Number) -> Number {
183        use crate::math::traits::MathBackend;
184        Self::backend().cos(x)
185    }
186
187    /// Calculate the tangent of an angle in radians
188    #[inline]
189    pub fn tan(x: Number) -> Number {
190        use crate::math::traits::MathBackend;
191        Self::backend().tan(x)
192    }
193
194    /// Convert degrees to radians
195    #[inline]
196    pub fn to_radians(degrees: Number) -> Number {
197        use crate::math::traits::MathBackend;
198        Self::backend().to_radians(degrees)
199    }
200
201    /// Convert radians to degrees
202    #[inline]
203    pub fn to_degrees(radians: Number) -> Number {
204        use crate::math::traits::MathBackend;
205        Self::backend().to_degrees(radians)
206    }
207
208    /// Calculate atan2(y, x) - the angle from the positive x-axis to the point (x, y)
209    #[inline]
210    pub fn atan2(y: Number, x: Number) -> Number {
211        use crate::math::traits::MathBackend;
212        Self::backend().atan2(y, x)
213    }
214}
215
216/// Type conversion utilities for different numeric types
217pub trait NumericConversion<T> {
218    /// Convert from the source type to Number
219    fn to_number(self) -> Number;
220    /// Convert from Number to the target type
221    fn from_number(n: Number) -> T;
222}
223
224// Implement conversions for common types
225impl NumericConversion<f32> for f32 {
226    #[inline]
227    fn to_number(self) -> Number {
228        #[cfg(feature = "floating-point")]
229        return self;
230
231        #[cfg(all(feature = "fixed-point", not(feature = "floating-point")))]
232        return fixed::types::I16F16::from_num(self);
233
234        #[cfg(all(
235            feature = "integer-math",
236            not(any(feature = "floating-point", feature = "fixed-point"))
237        ))]
238        return (self * 1000.0) as i32; // Scale by 1000 for integer representation
239
240        #[cfg(not(any(
241            feature = "floating-point",
242            feature = "fixed-point",
243            feature = "integer-math"
244        )))]
245        return self;
246    }
247
248    #[inline]
249    fn from_number(n: Number) -> f32 {
250        #[cfg(feature = "floating-point")]
251        return n;
252
253        #[cfg(all(feature = "fixed-point", not(feature = "floating-point")))]
254        return n.to_num();
255
256        #[cfg(all(
257            feature = "integer-math",
258            not(any(feature = "floating-point", feature = "fixed-point"))
259        ))]
260        return n as f32 / 1000.0; // Unscale from integer representation
261
262        #[cfg(not(any(
263            feature = "floating-point",
264            feature = "fixed-point",
265            feature = "integer-math"
266        )))]
267        return n;
268    }
269}
270
271impl NumericConversion<i32> for i32 {
272    #[inline]
273    fn to_number(self) -> Number {
274        #[cfg(feature = "floating-point")]
275        return self as f32;
276
277        #[cfg(all(feature = "fixed-point", not(feature = "floating-point")))]
278        return fixed::types::I16F16::from_num(self);
279
280        #[cfg(all(
281            feature = "integer-math",
282            not(any(feature = "floating-point", feature = "fixed-point"))
283        ))]
284        return self * 1000; // Scale by 1000 for precision
285
286        #[cfg(not(any(
287            feature = "floating-point",
288            feature = "fixed-point",
289            feature = "integer-math"
290        )))]
291        return self as f32;
292    }
293
294    #[inline]
295    fn from_number(n: Number) -> i32 {
296        #[cfg(feature = "floating-point")]
297        return n as i32;
298
299        #[cfg(all(feature = "fixed-point", not(feature = "floating-point")))]
300        return n.to_num();
301
302        #[cfg(all(
303            feature = "integer-math",
304            not(any(feature = "floating-point", feature = "fixed-point"))
305        ))]
306        return n / 1000; // Unscale from integer representation
307
308        #[cfg(not(any(
309            feature = "floating-point",
310            feature = "fixed-point",
311            feature = "integer-math"
312        )))]
313        return n as i32;
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    #[test]
322    #[cfg(not(feature = "integer-math"))] // Skip for integer-math to avoid precision issues
323    fn test_basic_math_operations() {
324        let a = 4.0f32.to_number();
325        let b = 2.0f32.to_number();
326
327        let sqrt_result = Math::sqrt(a);
328        let min_result = Math::min(a, b);
329        let max_result = Math::max(a, b);
330
331        // Convert back to f32 for comparison
332        assert!((f32::from_number(sqrt_result) - 2.0).abs() < 0.1);
333        assert!((f32::from_number(min_result) - 2.0).abs() < 0.1);
334        assert!((f32::from_number(max_result) - 4.0).abs() < 0.1);
335    }
336
337    #[test]
338    fn test_trigonometric_functions() {
339        let angle = 0.0f32.to_number();
340
341        let sin_result = Math::sin(angle);
342        let cos_result = Math::cos(angle);
343
344        // Convert back to f32 for comparison
345        assert!((f32::from_number(sin_result) - 0.0).abs() < 0.1);
346        assert!((f32::from_number(cos_result) - 1.0).abs() < 0.1);
347    }
348
349    #[test]
350    fn test_numeric_conversions() {
351        let original = core::f32::consts::PI;
352        let converted = original.to_number();
353        let back = f32::from_number(converted);
354
355        // Should be approximately equal (allowing for precision loss in integer modes)
356        assert!((original - back).abs() < 0.1);
357    }
358}