1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#![cfg_attr(not(any(test/*, feature = "std"*/)), no_std)]

#![feature(test)]
#![feature(const_trait_impl)]
#![feature(const_float_bits_conv)]
#![feature(const_fn_floating_point_arithmetic)]
#![feature(const_option)]
#![feature(const_mut_refs)]
#![feature(const_option_ext)]
#![feature(generic_arg_infer)]

#![feature(const_closures)]
#![feature(generic_const_exprs)]

moddef::moddef!(
    flat(pub) mod {
        approx_sqrt for cfg(feature = "sqrt"),
        approx_inv_sqrt for cfg(feature = "sqrt"),

        approx_sin for cfg(feature = "sin"),
        approx_cos for cfg(feature = "sin")
    },
    mod {
        plot for cfg(test)
    }
);

mod f32
{
    pub use core::f32::consts::*;

    #[allow(unused)]
    pub(crate) const EXP_BIAS: u32 = 127;
}
mod f64
{
    pub use core::f64::consts::*;

    #[allow(unused)]
    pub(crate) const EXP_BIAS: u64 = 1023;
}

#[cfg(test)]
extern crate test;

#[cfg(test)]
mod tests {
    use std::{time::Duration, ops::RangeBounds};

    use array__ops::{ArrayOps, Array2dOps};
    use linspace::{LinspaceArray, Linspace};
    
    const PLOT_TARGET: &str = "plots";

    use super::*;

    #[allow(unused)]
    pub fn plot_benchmark<T, R, const N: usize, const M: usize>(
        fn_name: &str,
        func: [&dyn Fn(T) -> R; M],
        x: impl RangeBounds<T> + Linspace<T> + Clone,
        smoothing: usize
    ) -> ()
    where
        T: Clone
    {
        let n: [usize; N] = (1..=N).linspace_array();
        let t = n
            .map(move |n| {
                let x = x.clone()
                    .linspace(n);
                func.map(|f| (0..smoothing).map(|_| benchmark(&x, f).as_secs_f32())
                    .reduce(|a, b| a + b)
                    .unwrap()/smoothing as f32
                )
            }).transpose();
        
        let plot_title: &str = &format!("{fn_name}(x) benchmark");
        let plot_path: &str = &format!("{PLOT_TARGET}/{fn_name}_benchmark.png");
        
        plot::plot_curves(plot_title, plot_path, [n.map(|n| n as f32); M], t).expect("Plot error")
    }

    pub fn benchmark<T, R>(x: &[T], f: &dyn Fn(T) -> R) -> Duration
    where
        T: Clone
    {
        use std::time::SystemTime;

        let x = x.to_vec();
        let t0 = SystemTime::now();
        x.into_iter().for_each(|x| {f(x);});
        t0.elapsed().unwrap()
    }

    const N: usize = 2048;

    #[allow(unused)]
    pub fn plot_approx<R>(
        fn_name: &str,
        range: R,
        func: impl Fn(f32) -> f32,
        approx: impl Fn(f32) -> f32
    )
    where
        R: RangeBounds<f32> + LinspaceArray<f32, N>
    {
        let x: [f32; N] = range.linspace_array();
        let y_approx = x.map(approx);

        let y = x.map(func);

        let plot_title: &str = &format!("{fn_name}(x)");
        let plot_path: &str = &format!("{PLOT_TARGET}/{fn_name}.png");

        plot::plot_curves(plot_title, plot_path, [x, x], [y, y_approx])
            .expect("Plot error");

        let (avg_error, max_abs_error) = y.zip(y_approx)
            .map(|(y, y_approx)| y - y_approx)
            .map(|y| (y, y.abs()))
            .reduce(|a, b| (a.0 + b.0, a.1.max(b.1)))
            .map(|(sum_error, max_abs_error)| (sum_error/N as f32, max_abs_error))
            .unwrap_or_default();
        println!("Average Error: {}", avg_error);
        println!("Max |Error|: {}", max_abs_error);
    }
}