exp_rs/
functions.rs

1//! Built-in mathematical functions for expression evaluation.
2//!
3//! This module provides the implementation of all built-in functions that can be used
4//! in expressions. These include common mathematical operations like trigonometric functions,
5//! logarithms, exponentials, and more. The functions handle special cases like division
6//! by zero and out-of-range inputs gracefully by returning appropriate values like NaN
7//! or infinity.
8//!
9//! All functions use the `libm` crate for their implementations, which ensures
10//! compatibility with no_std environments. Depending on the selected floating-point
11//! precision (f32 or f64, controlled by the "f32" feature), different versions of the
12//! math functions are used.
13
14#[cfg(feature = "f32")]
15use libm::{
16    acosf as libm_acos, asinf as libm_asin, atan2f as libm_atan2, atanf as libm_atan,
17    ceilf as libm_ceil, cosf as libm_cos, coshf as libm_cosh, expf as libm_exp,
18    floorf as libm_floor, log10f as libm_log10, logf as libm_ln, powf as libm_pow,
19    sinf as libm_sin, sinhf as libm_sinh, sqrtf as libm_sqrt, tanf as libm_tan, tanhf as libm_tanh,
20};
21
22#[cfg(not(feature = "f32"))]
23use libm::{
24    acos as libm_acos, asin as libm_asin, atan as libm_atan, atan2 as libm_atan2,
25    ceil as libm_ceil, cos as libm_cos, cosh as libm_cosh, exp as libm_exp, floor as libm_floor,
26    log as libm_ln, log10 as libm_log10, pow as libm_pow, sin as libm_sin, sinh as libm_sinh,
27    sqrt as libm_sqrt, tan as libm_tan, tanh as libm_tanh,
28};
29
30use crate::Real;
31
32/// Dummy function that panics when called.
33///
34/// This function is used internally as a placeholder for uninitialized functions.
35/// It should never be called in normal operation.
36pub fn dummy(_: Real, _: Real) -> Real {
37    panic!("called dummy!")
38}
39
40/// Returns the maximum of two values.
41///
42/// # Parameters
43///
44/// * `a` - First value
45/// * `b` - Second value
46///
47/// # Returns
48///
49/// The larger of `a` and `b`.
50pub fn max(a: Real, b: Real) -> Real {
51    if a > b {
52        a
53    } else {
54        b
55    }
56}
57
58/// Adds two values.
59///
60/// # Parameters
61///
62/// * `a` - First value
63/// * `b` - Second value
64///
65/// # Returns
66///
67/// The sum of `a` and `b`.
68pub fn add(a: Real, b: Real) -> Real {
69    a + b
70}
71
72/// Returns the minimum of two values.
73///
74/// # Parameters
75///
76/// * `a` - First value
77/// * `b` - Second value
78///
79/// # Returns
80///
81/// The smaller of `a` and `b`.
82pub fn min(a: Real, b: Real) -> Real {
83    if a < b {
84        a
85    } else {
86        b
87    }
88}
89
90/// Subtracts the second value from the first.
91///
92/// # Parameters
93///
94/// * `a` - Value to subtract from
95/// * `b` - Value to subtract
96///
97/// # Returns
98///
99/// The difference `a - b`.
100pub fn sub(a: Real, b: Real) -> Real {
101    a - b
102}
103
104/// Multiplies two values.
105///
106/// # Parameters
107///
108/// * `a` - First value
109/// * `b` - Second value
110///
111/// # Returns
112///
113/// The product of `a` and `b`.
114pub fn mul(a: Real, b: Real) -> Real {
115    a * b
116}
117
118/// Divides the first value by the second.
119///
120/// This function handles division by zero gracefully by returning:
121/// - NaN for 0/0
122/// - Positive infinity for positive/0
123/// - Negative infinity for negative/0
124///
125/// # Parameters
126///
127/// * `a` - Numerator
128/// * `b` - Denominator
129///
130/// # Returns
131///
132/// The quotient `a / b`, or appropriate value for division by zero.
133pub fn div(a: Real, b: Real) -> Real {
134    if b == 0.0 {
135        if a == 0.0 {
136            #[cfg(feature = "f32")]
137            return f32::NAN; // 0/0 is NaN
138            #[cfg(not(feature = "f32"))]
139            return f64::NAN; // 0/0 is NaN
140        } else if a > 0.0 {
141            #[cfg(feature = "f32")]
142            return f32::INFINITY; // Positive number divided by zero is positive infinity
143            #[cfg(not(feature = "f32"))]
144            return f64::INFINITY; // Positive number divided by zero is positive infinity
145        } else {
146            #[cfg(feature = "f32")]
147            return f32::NEG_INFINITY; // Negative number divided by zero is negative infinity
148            #[cfg(not(feature = "f32"))]
149            return f64::NEG_INFINITY; // Negative number divided by zero is negative infinity
150        }
151    } else {
152        a / b
153    }
154}
155pub fn fmod(a: Real, b: Real) -> Real {
156    a % b
157}
158pub fn neg(a: Real, _: Real) -> Real {
159    -a
160}
161pub fn comma(_: Real, b: Real) -> Real {
162    b
163}
164pub fn abs(a: Real, _: Real) -> Real {
165    a.abs()
166}
167pub fn acos(a: Real, _: Real) -> Real {
168    if !(-1.0..=1.0).contains(&a) {
169        #[cfg(feature = "f32")]
170        return f32::NAN; // acos is only defined for inputs between -1 and 1
171        #[cfg(not(feature = "f32"))]
172        return f64::NAN; // acos is only defined for inputs between -1 and 1
173    } else {
174        libm_acos(a)
175    }
176}
177pub fn asin(a: Real, _: Real) -> Real {
178    if !(-1.0..=1.0).contains(&a) {
179        #[cfg(feature = "f32")]
180        return f32::NAN; // asin is only defined for inputs between -1 and 1
181        #[cfg(not(feature = "f32"))]
182        return f64::NAN; // asin is only defined for inputs between -1 and 1
183    } else {
184        libm_asin(a)
185    }
186}
187pub fn atan(a: Real, _: Real) -> Real {
188    libm_atan(a)
189}
190pub fn atan2(a: Real, b: Real) -> Real {
191    // atan2 takes y,x order (not x,y)
192    // In our function signature:
193    // a is the first argument
194    // b is the second argument
195    // libm_atan2 expects (y,x) order
196    #[cfg(test)]
197    println!("atan2 called with a={}, b={}", a, b);
198
199    let result = libm_atan2(a, b); // Don't swap the arguments
200
201    #[cfg(test)]
202    println!("atan2 result: {}", result);
203
204    result
205}
206pub fn ceil(a: Real, _: Real) -> Real {
207    libm_ceil(a)
208}
209pub fn cos(a: Real, _: Real) -> Real {
210    libm_cos(a)
211}
212pub fn cosh(a: Real, _: Real) -> Real {
213    libm_cosh(a)
214}
215pub fn e(_: Real, _: Real) -> Real {
216    crate::constants::E
217}
218pub fn exp(a: Real, _: Real) -> Real {
219    libm_exp(a)
220}
221pub fn floor(a: Real, _: Real) -> Real {
222    libm_floor(a)
223}
224pub fn ln(a: Real, _: Real) -> Real {
225    if a <= 0.0 {
226        #[cfg(feature = "f32")]
227        return f32::NAN; // Natural log of zero or negative number is undefined
228        #[cfg(not(feature = "f32"))]
229        return f64::NAN; // Natural log of zero or negative number is undefined
230    } else {
231        libm_ln(a)
232    }
233}
234pub fn log(a: Real, _: Real) -> Real {
235    if a <= 0.0 {
236        #[cfg(feature = "f32")]
237        return f32::NAN; // Log of zero or negative number is undefined
238        #[cfg(not(feature = "f32"))]
239        return f64::NAN; // Log of zero or negative number is undefined
240    } else {
241        libm_log10(a)
242    }
243}
244pub fn log10(a: Real, _: Real) -> Real {
245    if a <= 0.0 {
246        #[cfg(feature = "f32")]
247        return f32::NAN; // Log10 of zero or negative number is undefined
248        #[cfg(not(feature = "f32"))]
249        return f64::NAN; // Log10 of zero or negative number is undefined
250    } else {
251        libm_log10(a)
252    }
253}
254pub fn pi(_: Real, _: Real) -> Real {
255    crate::constants::PI
256}
257/// Raises a value to a power.
258///
259/// This function computes `a` raised to the power of `b` (a^b).
260/// It handles various special cases and edge conditions:
261///
262/// - 0^0 = 1 (by mathematical convention)
263/// - Negative base with non-integer exponent returns NaN
264/// - Very large exponents that would cause overflow return infinity
265/// - Very small values that would cause underflow return 0
266///
267/// # Parameters
268///
269/// * `a` - Base value
270/// * `b` - Exponent
271///
272/// # Returns
273///
274/// The value of `a` raised to the power of `b`.
275pub fn pow(a: Real, b: Real) -> Real {
276    #[cfg(test)]
277    println!("pow function called with a={}, b={}", a, b);
278
279    // Handle special cases
280    if a == 0.0 && b == 0.0 {
281        return 1.0; // 0^0 = 1 by convention
282    }
283
284    if a < 0.0 && b != libm_floor(b) {
285        // Negative base with non-integer exponent is not a real number
286        #[cfg(feature = "f32")]
287        return f32::NAN;
288        #[cfg(not(feature = "f32"))]
289        return f64::NAN;
290    }
291
292    // Check for potential overflow
293    #[cfg(feature = "f32")]
294    if a.abs() > 1.0 && b > 88.0 {
295        return if a > 0.0 {
296            f32::INFINITY
297        } else {
298            f32::NEG_INFINITY
299        };
300    }
301
302    #[cfg(not(feature = "f32"))]
303    if a.abs() > 1.0 && b > 709.0 {
304        return if a > 0.0 {
305            f64::INFINITY
306        } else {
307            f64::NEG_INFINITY
308        };
309    }
310
311    #[cfg(feature = "f32")]
312    if a.abs() < 1.0 && b < -88.0 {
313        return 0.0; // Underflow to zero
314    }
315
316    #[cfg(not(feature = "f32"))]
317    if a.abs() < 1.0 && b < -709.0 {
318        return 0.0; // Underflow to zero
319    }
320
321    libm_pow(a, b)
322}
323pub fn sin(a: Real, _: Real) -> Real {
324    libm_sin(a)
325}
326pub fn sinh(a: Real, _: Real) -> Real {
327    libm_sinh(a)
328}
329pub fn sqrt(a: Real, _: Real) -> Real {
330    if a < 0.0 {
331        #[cfg(feature = "f32")]
332        return f32::NAN; // Square root of negative number is NaN
333        #[cfg(not(feature = "f32"))]
334        return f64::NAN; // Square root of negative number is NaN
335    } else {
336        libm_sqrt(a)
337    }
338}
339pub fn tan(a: Real, _: Real) -> Real {
340    libm_tan(a)
341}
342pub fn tanh(a: Real, _: Real) -> Real {
343    libm_tanh(a)
344}
345
346pub fn sign(a: Real, _: Real) -> Real {
347    if a > 0.0 {
348        1.0
349    } else if a < 0.0 {
350        -1.0
351    } else {
352        0.0
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    #[should_panic(expected = "called dummy!")]
362    fn test_dummy_panics() {
363        dummy(1.0, 2.0);
364    }
365
366    #[test]
367    fn test_sub() {
368        assert_eq!(sub(5.0, 3.0), 2.0);
369    }
370
371    #[test]
372    fn test_mul() {
373        assert_eq!(mul(2.0, 3.0), 6.0);
374    }
375
376    #[test]
377    fn test_div() {
378        assert_eq!(div(6.0, 3.0), 2.0);
379    }
380
381    #[test]
382    fn test_fmod() {
383        assert_eq!(fmod(7.0, 4.0), 3.0);
384    }
385
386    #[test]
387    fn test_neg() {
388        assert_eq!(neg(5.0, 0.0), -5.0);
389    }
390
391    #[test]
392    fn test_comma() {
393        assert_eq!(comma(1.0, 2.0), 2.0);
394    }
395
396    #[test]
397    fn test_abs() {
398        assert_eq!(abs(-5.0, 0.0), 5.0);
399    }
400
401    #[test]
402    fn test_acos() {
403        assert!((acos(1.0, 0.0) - 0.0).abs() < 1e-10);
404    }
405
406    #[test]
407    fn test_asin() {
408        assert!((asin(0.0, 0.0) - 0.0).abs() < 1e-10);
409    }
410
411    #[test]
412    fn test_atan() {
413        assert!((atan(0.0, 0.0) - 0.0).abs() < 1e-10);
414    }
415
416    #[test]
417    fn test_atan2() {
418        // Test basic cases with direct function calls
419        #[cfg(feature = "f32")]
420        assert!((atan2(1.0, 1.0) - core::f32::consts::FRAC_PI_4).abs() < 1e-6);
421        #[cfg(not(feature = "f32"))]
422        assert!((atan2(1.0, 1.0) - core::f64::consts::FRAC_PI_4).abs() < 1e-10);
423
424        // Test more cases to verify the function works correctly
425        // atan2(y, x) where:
426
427        // Quadrant 1: y > 0, x > 0
428        assert!(
429            (atan2(1.0, 2.0) - 0.4636476090008061).abs() < 1e-10,
430            "atan2(1.0, 2.0) should be approximately 0.4636"
431        );
432
433        // Quadrant 2: y > 0, x < 0
434        #[cfg(feature = "f32")]
435        assert!(
436            (atan2(1.0, -1.0) - (3.0 * core::f32::consts::FRAC_PI_4)).abs() < 1e-6,
437            "atan2(1.0, -1.0) should be approximately 3π/4"
438        );
439        #[cfg(not(feature = "f32"))]
440        assert!(
441            (atan2(1.0, -1.0) - (3.0 * core::f64::consts::FRAC_PI_4)).abs() < 1e-10,
442            "atan2(1.0, -1.0) should be approximately 3π/4"
443        );
444
445        // Quadrant 3: y < 0, x < 0
446        #[cfg(feature = "f32")]
447        assert!(
448            (atan2(-1.0, -1.0) + (3.0 * core::f32::consts::FRAC_PI_4)).abs() < 1e-6,
449            "atan2(-1.0, -1.0) should be approximately -3π/4"
450        );
451        #[cfg(not(feature = "f32"))]
452        assert!(
453            (atan2(-1.0, -1.0) + (3.0 * core::f64::consts::FRAC_PI_4)).abs() < 1e-10,
454            "atan2(-1.0, -1.0) should be approximately -3π/4"
455        );
456
457        // Quadrant 4: y < 0, x > 0
458        #[cfg(feature = "f32")]
459        assert!(
460            (atan2(-1.0, 1.0) + core::f32::consts::FRAC_PI_4).abs() < 1e-6,
461            "atan2(-1.0, 1.0) should be approximately -π/4"
462        );
463        #[cfg(not(feature = "f32"))]
464        assert!(
465            (atan2(-1.0, 1.0) + core::f64::consts::FRAC_PI_4).abs() < 1e-10,
466            "atan2(-1.0, 1.0) should be approximately -π/4"
467        );
468
469        // Special cases
470        assert!(
471            (atan2(0.0, 1.0) - 0.0).abs() < 1e-10,
472            "atan2(0.0, 1.0) should be 0"
473        );
474
475        #[cfg(feature = "f32")]
476        assert!(
477            (atan2(1.0, 0.0) - core::f32::consts::FRAC_PI_2).abs() < 1e-6,
478            "atan2(1.0, 0.0) should be π/2"
479        );
480        #[cfg(not(feature = "f32"))]
481        assert!(
482            (atan2(1.0, 0.0) - core::f64::consts::FRAC_PI_2).abs() < 1e-10,
483            "atan2(1.0, 0.0) should be π/2"
484        );
485
486        #[cfg(feature = "f32")]
487        assert!(
488            (atan2(0.0, -1.0) - core::f32::consts::PI).abs() < 1e-6,
489            "atan2(0.0, -1.0) should be π"
490        );
491        #[cfg(not(feature = "f32"))]
492        assert!(
493            (atan2(0.0, -1.0) - core::f64::consts::PI).abs() < 1e-10,
494            "atan2(0.0, -1.0) should be π"
495        );
496
497        #[cfg(feature = "f32")]
498        assert!(
499            (atan2(-1.0, 0.0) + core::f32::consts::FRAC_PI_2).abs() < 1e-6,
500            "atan2(-1.0, 0.0) should be -π/2"
501        );
502        #[cfg(not(feature = "f32"))]
503        assert!(
504            (atan2(-1.0, 0.0) + core::f64::consts::FRAC_PI_2).abs() < 1e-10,
505            "atan2(-1.0, 0.0) should be -π/2"
506        );
507
508        // Print debug information
509        println!("atan2(1.0, 2.0) = {}", atan2(1.0, 2.0));
510        println!("atan2(2.0, 1.0) = {}", atan2(2.0, 1.0));
511    }
512
513    #[test]
514    fn test_ceil() {
515        assert_eq!(ceil(2.3, 0.0), 3.0);
516    }
517
518    #[test]
519    fn test_cos() {
520        assert!((cos(0.0, 0.0) - 1.0).abs() < 1e-10);
521    }
522
523    #[test]
524    fn test_cosh() {
525        assert!((cosh(0.0, 0.0) - 1.0).abs() < 1e-10);
526    }
527
528    #[test]
529    fn test_e() {
530        #[cfg(feature = "f32")]
531        assert!((e(0.0, 0.0) - core::f32::consts::E).abs() < 1e-6);
532        #[cfg(not(feature = "f32"))]
533        assert!((e(0.0, 0.0) - core::f64::consts::E).abs() < 1e-10);
534    }
535
536    #[test]
537    fn test_exp() {
538        #[cfg(feature = "f32")]
539        assert!((exp(1.0, 0.0) - core::f32::consts::E).abs() < 1e-6);
540        #[cfg(not(feature = "f32"))]
541        assert!((exp(1.0, 0.0) - core::f64::consts::E).abs() < 1e-10);
542    }
543
544    #[test]
545    fn test_floor() {
546        assert_eq!(floor(2.7, 0.0), 2.0);
547    }
548
549    #[test]
550    fn test_ln() {
551        #[cfg(feature = "f32")]
552        assert!((ln(core::f32::consts::E, 0.0) - 1.0).abs() < 1e-6);
553        #[cfg(not(feature = "f32"))]
554        assert!((ln(core::f64::consts::E, 0.0) - 1.0).abs() < 1e-10);
555    }
556
557    #[test]
558    fn test_log() {
559        assert!((log(1000.0, 0.0) - 3.0).abs() < 1e-10);
560    }
561
562    #[test]
563    fn test_log10() {
564        assert!((log10(1000.0, 0.0) - 3.0).abs() < 1e-10);
565    }
566
567    #[test]
568    fn test_pi() {
569        #[cfg(feature = "f32")]
570        assert!((pi(0.0, 0.0) - core::f32::consts::PI).abs() < 1e-6);
571        #[cfg(not(feature = "f32"))]
572        assert!((pi(0.0, 0.0) - core::f64::consts::PI).abs() < 1e-10);
573    }
574
575    #[test]
576    fn test_pow() {
577        assert_eq!(pow(2.0, 3.0), 8.0);
578    }
579
580    #[test]
581    fn test_sin() {
582        assert!((sin(0.0, 0.0) - 0.0).abs() < 1e-10);
583    }
584
585    #[test]
586    fn test_sinh() {
587        assert!((sinh(0.0, 0.0) - 0.0).abs() < 1e-10);
588    }
589
590    #[test]
591    fn test_sqrt() {
592        assert_eq!(sqrt(4.0, 0.0), 2.0);
593    }
594
595    #[test]
596    fn test_tan() {
597        assert!((tan(0.0, 0.0) - 0.0).abs() < 1e-10);
598    }
599
600    #[test]
601    fn test_tanh() {
602        assert!((tanh(0.0, 0.0) - 0.0).abs() < 1e-10);
603    }
604
605    #[test]
606    fn test_sign() {
607        assert_eq!(sign(5.0, 0.0), 1.0);
608        assert_eq!(sign(-3.0, 0.0), -1.0);
609        assert_eq!(sign(0.0, 0.0), 0.0);
610    }
611}