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
use super::data::POLY_POW10;
use super::pow::{pow_reduce, square_mul};
use crate::float::{EPSILON, F};
use crate::utils::{nearly_equal, poly};

/// Computes 10 raised to a power.
///
/// # Notes
///
/// The input domain is limited to approximately [log10(min(positive f32)),
/// log10(max(f32))] ≈ [-37.9, 38.5] due to limits of machine representation.
///
/// # Examples
///
/// ```
/// use nikisas::pow10;
/// assert_eq!(pow10(-1.0), 0.1);
/// ```
///
/// # Implementation details
///
/// First, the special case when x is near zero is handled such that the result
/// is simply 1. Otherwise, the input x is reduced to an integer k and real y
/// such that
///
/// ```plain
///   x = k + y and |y| ≤ 1/2
/// ```
///
/// Let us denote z = |y|. Approximation of 10^z is done using polynomial in the
/// form:
///
/// ```plain
///   10^z ≈ 1 + z * P(z)
/// ```
///
/// The "prefix" corresponds to coefficients of low-degree Taylor polynomial of
/// 10^z for z = 0 and P is found using special minimax algorithm in Sollya.
///
/// Now we have
///
/// ```plain
///   10^y = if y ≥ 0 then 10^z else 1 / 10^z
/// ```
///
/// The reconstruction of original value is then
///
/// ```plain
/// 10^x = 10^(k + y) = 10^k * 10^y
/// ```
///
/// Computation of 10^y is (transitively) done using aforementioned polynomial
/// approximation and multiply-and-square loop algorithm is used for computation
/// of 10^k. Note that in this case, the maximum number of iterations is limited
/// by log2(max(|input range of x|)) < 6.
pub fn pow10(p: F) -> F {
    if nearly_equal(p, 0.0, EPSILON) {
        return 1.0;
    }

    let (k, z, inv) = pow_reduce(p);

    let pow10z = 1.0 + z * poly(z, POLY_POW10);
    let pow10z = if inv { 1.0 / pow10z } else { pow10z };

    square_mul(10.0, k) * pow10z
}

#[cfg(test)]
mod tests {
    use crate::float::F;
    use crate::test::error_bounds;
    use nikisas_test::prelude::*;

    #[test]
    fn pow10() {
        (0..32)
            .fold(Error::with_bounds(error_bounds()), |mut error, k| {
                let y = 10.0f32.powi(k);
                error.calculate(y, super::pow10(k as F), y);
                error
            })
            .assert();

        UniformSample::with_count(-0.5, 0.5, 100000)
            .assert(error_bounds(), |x| (super::pow10(x), 10.0f32.powf(x)));

        UniformSample::with_count(-37.9, 38.5, 10000)
            .assert(error_bounds(), |x| (super::pow10(x), 10.0f32.powf(x)));
    }
}