inari 2.0.0

A Rust implementation of interval arithmetic
Documentation
// An interval extension of exp(x), exp2(x) and exp10(x). Not tightest, but simple.

#![allow(clippy::approx_constant)]

use inari::*;

// To compute exp(x), change the base to 2 using the relation:
//
//   exp(x) = 2^(lg(e) x).
//
// Now consider computing 2^x. Let D = [-1/2, 1/2].
// Decompose x into an integer and a fractional parts:
//
//   x = a + b,
//
// where a ∈ ℤ, b ∈ D. Therefore, 2^x = 2^a 2^b.
// From the Taylor polynomial of 2^b at b = 0 and its Lagrange remainder, we have:
//
//           N-1 ln(2)^n       ln(2)^N
//   2^b ∈ {  ∑  ------- b^n + ------- b^N 2^β ∣ β ∈ D}, ∀b ∈ D.
//           n=0   n!            N!
//
// From the following relations, N = 14 would be sufficient:
//
//         | ln(2)^N         |   ln(2)^N
//    max  | ------- b^N 2^β | = -------- (1/2)^N √2,
//   b,β∈D |   N!            |     N!
//
//   ln(2)^N            |
//   ------- (1/2)^N √2 |     ≤ 2^-53.
//     N!               |N=14
//
// References:
//
// - Oishi, S., & Kashiwagi M. (2018). 数学関数の精度保証. In S. Oishi (Ed.), 精度保証付き数値計算の基礎 (pp. 91-107). コロナ社.

fn exp2_point_reduced(x: f64) -> Interval {
    let x = interval!(x, x).unwrap();
    assert!(x.subset(const_interval!(-0.5, 0.5)));

    const N: usize = 14;
    // C[n] = ln(2)^n / n! for n = 0, …, N - 1.
    const C: [Interval; N] = [
        const_interval!(1.0, 1.0),
        const_interval!(0.6931471805599453, 0.6931471805599454),
        const_interval!(0.2402265069591007, 0.24022650695910072),
        const_interval!(5.5504108664821576e-2, 5.550410866482158e-2),
        const_interval!(9.618129107628477e-3, 9.618129107628479e-3),
        const_interval!(1.3333558146428443e-3, 1.3333558146428445e-3),
        const_interval!(1.540353039338161e-4, 1.5403530393381611e-4),
        const_interval!(1.525273380405984e-5, 1.5252733804059841e-5),
        const_interval!(1.3215486790144307e-6, 1.321548679014431e-6),
        const_interval!(1.0178086009239699e-7, 1.01780860092397e-7),
        const_interval!(7.0549116208011225e-9, 7.054911620801123e-9),
        const_interval!(4.445538271870811e-10, 4.4455382718708116e-10),
        const_interval!(2.5678435993488202e-11, 2.5678435993488206e-11),
        const_interval!(1.3691488853904128e-12, 1.369148885390413e-12),
    ];

    // Initially, y = 2^D ln(2)^N / N!.
    let mut y = const_interval!(4.7932833733029885e-14, 9.586566746605978e-14);
    for n in (0..N).rev() {
        y = y.mul_add(x, C[n]);
    }
    y
}

fn exp2_point(x: f64) -> Interval {
    assert!(x.is_finite());

    let mut a = x.floor();
    let mut b = x - a;
    if b > 0.5 {
        a += 1.0;
        b -= 1.0;
    }

    let exp2_a = if a < -1074.0 {
        const_interval!(0.0, 5e-324)
    } else if a > 1023.0 {
        const_interval!(f64::MAX, f64::INFINITY)
    } else {
        let exp2_a = libm::ldexp(1.0, a as i32);
        interval!(exp2_a, exp2_a).unwrap()
    };

    let exp2_b = exp2_point_reduced(b);

    exp2_a * exp2_b
}

fn exp2(x: Interval) -> Interval {
    if x.is_empty() {
        return x;
    }

    let inf = if x.inf() == f64::NEG_INFINITY {
        0.0
    } else {
        exp2_point(x.inf()).inf()
    };
    let sup = if x.sup() == f64::INFINITY {
        f64::INFINITY
    } else {
        exp2_point(x.sup()).sup()
    };
    interval!(inf, sup).unwrap()
}

fn exp(x: Interval) -> Interval {
    exp2(Interval::LOG2_E * x)
}

fn exp10(x: Interval) -> Interval {
    exp2(Interval::LOG2_10 * x)
}

fn main() {
    let x = interval!("[1.234567]").unwrap();

    println!("2^x ⊆ {:.15e}", exp2(x));
    println!("2^x ⊆ {:.15e} (MPFR)", x.exp2());
    assert!(x.exp2().subset(exp2(x)));

    println!("e^x ⊆ {:.15e}", exp(x));
    println!("e^x ⊆ {:.15e} (MPFR)", x.exp());
    assert!(x.exp().subset(exp(x)));

    println!("10^x ⊆ {:.15e}", x.exp10());
    println!("10^x ⊆ {:.15e} (MPFR)", exp10(x));
    assert!(x.exp10().subset(exp10(x)));
}