/*
 * // Copyright (c) Radzivon Bartoshyk 7/2025. All rights reserved.
 * //
 * // Redistribution and use in source and binary forms, with or without modification,
 * // are permitted provided that the following conditions are met:
 * //
 * // 1.  Redistributions of source code must retain the above copyright notice, this
 * // list of conditions and the following disclaimer.
 * //
 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
 * // this list of conditions and the following disclaimer in the documentation
 * // and/or other materials provided with the distribution.
 * //
 * // 3.  Neither the name of the copyright holder nor the names of its
 * // contributors may be used to endorse or promote products derived from
 * // this software without specific prior written permission.
 * //
 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
use crate::common::{dd_fmla, f_fmla};
use crate::double_double::DoubleDouble;
use crate::dyadic_float::{DyadicFloat128, DyadicSign};
use crate::exponents::{EXP_REDUCE_T0, EXP_REDUCE_T1, rational128_exp};
use crate::polyeval::{f_estrin_polyeval5, f_polyeval4};

/// Modified Bessel of the first kind of order 0
///
/// Max ULP 0.5
pub fn f_i0(x: f64) -> f64 {
    let xb = x.to_bits() & 0x7fff_ffff_ffff_ffff;

    if !x.is_normal() {
        if x == 0. {
            return 1.;
        }
        if x.is_infinite() {
            return f64::INFINITY;
        }
        if x.is_nan() {
            return f64::NAN;
        }
    }

    if xb > 0x40864fe5304e83e4u64 {
        // 713.9869085439682
        return f64::INFINITY;
    }

    if xb <= 0x400ccccccccccccdu64 {
        // x <= 3.6
        if xb <= 0x3cb0000000000000u64 {
            // x <= f64::EPSILON
            // Power series of I0(x) ~ 1 + x^2/4 + O(x^4)
            const R: f64 = 1. / 4.;
            let r = f_fmla(x, x * R, 1.);
            return r;
        }
        return i0_0_to_3p6_exec(f64::from_bits(xb));
    } else if xb <= 0x401e000000000000u64 {
        // x <= 7.5
        return i3p6_to_7p5(f64::from_bits(xb));
    } else if xb <= 0x4023000000000000u64 {
        // 9.5
        return i0_7p5_to_9p5(f64::from_bits(xb));
    }

    i0_asympt(f64::from_bits(xb))
}

/**
Computes I0 on interval [-7.5; -3.6], [3.6; 7.5]
**/
#[inline]
fn i3p6_to_7p5(x: f64) -> f64 {
    let r = i0_3p6_to_7p5_dd(x);

    let err = f_fmla(
        r.hi,
        f64::from_bits(0x3c56a09e667f3bcd), // 2^-57.5
        f64::from_bits(0x3c00000000000000), // 2^-63
    );
    let ub = r.hi + (r.lo + err);
    let lb = r.hi + (r.lo - err);
    if ub != lb {
        return eval_small_hard_3p6_to_7p5(x);
    }
    r.to_f64()
}

/**
Computes I0 on interval [-7.5; -3.6], [3.6; 7.5]
as rational approximation I0 = 1 + (x/2)^2 * Pn((x/2)^2)/Qm((x/2)^2))

Relative error on interval 2^-(105.71).

Generated by Wolfram Mathematica:
```text
<<FunctionApproximations`
ClearAll["Global`*"]
f[x_]:=(BesselI[0,x]-1)/(x/2)^2
g[z_]:=f[2 Sqrt[z]]
{err, approx}=MiniMaxApproximation[g[z],{z,{0.0000000000001,3.6},7,6},WorkingPrecision->60]
poly=Numerator[approx][[1]];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
poly=Denominator[approx][[1]];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
```
**/
#[inline]
fn i0_0_to_3p6_dd(x: f64) -> DoubleDouble {
    const ONE_OVER_4: f64 = 1. / 4.;
    let eval_x = DoubleDouble::quick_mult_f64(DoubleDouble::from_exact_mult(x, x), ONE_OVER_4);
    const P: [(u64, u64); 8] = [
        (0xba93dec1e5396e30, 0x3ff0000000000000),
        (0xbc5d3d919a2b891a, 0x3fcb128f49a1f59f),
        (0xbc3c4d80de165459, 0x3f9353508fce278f),
        (0x3be7e0e75c00d411, 0x3f4b760657892e15),
        (0xbb9bc959d02076a4, 0x3ef588ff0ba5872e),
        (0x3b257756675180e4, 0x3e932e320d411521),
        (0xbaca098436a47ec6, 0x3e2285f524a51de0),
        (0x3a0e48fa0331db75, 0x3d9ee6518d82a209),
    ];
    let ps_num = f_estrin_polyeval5(
        eval_x.hi,
        f64::from_bits(0x3f4b760657892e15),
        f64::from_bits(0x3ef588ff0ba5872e),
        f64::from_bits(0x3e932e320d411521),
        f64::from_bits(0x3e2285f524a51de0),
        f64::from_bits(0x3d9ee6518d82a209),
    );
    let mut p_num = DoubleDouble::mul_f64_add(eval_x, ps_num, DoubleDouble::from_bit_pair(P[2]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[1]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[0]));
    const Q: [(u64, u64); 7] = [
        (0x0000000000000000, 0x3ff0000000000000),
        (0x3c26136ec7050a58, 0xbfa3b5c2d9782985),
        (0x3bdf9cd5be66297b, 0x3f478d5c030ea692),
        (0xbb5036196d4b865c, 0xbee1a83b6e8c6fd6),
        (0xbb1a9dafadc75858, 0x3e71ba443893032b),
        (0xba7a719ba9ed7e7f, 0xbdf6e673af3e0c66),
        (0xb9e17740b37a23ec, 0x3d6e2c993fef696f),
    ];
    let ps_den = f_polyeval4(
        eval_x.hi,
        f64::from_bits(0xbee1a83b6e8c6fd6),
        f64::from_bits(0x3e71ba443893032b),
        f64::from_bits(0xbdf6e673af3e0c66),
        f64::from_bits(0x3d6e2c993fef696f),
    );
    let mut p_den = DoubleDouble::mul_f64_add(eval_x, ps_den, DoubleDouble::from_bit_pair(Q[2]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[1]));
    p_den = DoubleDouble::mul_add_f64(eval_x, p_den, f64::from_bits(0x3ff0000000000000));
    DoubleDouble::mul_add_f64(DoubleDouble::div(p_num, p_den), eval_x, 1.)
}

// Generated by Wolfram Mathematica:
// I0(x) = 1+(x/2)^2 * R((x/2)^2)
// <<FunctionApproximations`
// ClearAll["Global`*"]
// f[x_]:=(BesselI[0,x]-1)/(x/2)^2
// g[z_]:=f[2 Sqrt[z]]
// {err, approx}=MiniMaxApproximation[g[z],{z,{3.6,7.51},8,8},WorkingPrecision->60]
// poly=Numerator[approx][[1]];
// coeffs=CoefficientList[poly,z];
// TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
// poly=Denominator[approx][[1]];
// coeffs=CoefficientList[poly,z];
// TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
#[inline]
fn i0_3p6_to_7p5_dd(x: f64) -> DoubleDouble {
    const ONE_OVER_4: f64 = 1. / 4.;
    let eval_x = DoubleDouble::quick_mult_f64(DoubleDouble::from_exact_mult(x, x), ONE_OVER_4);
    const P: [(u64, u64); 10] = [
        (0xbae8195e94c833a1, 0x3ff0000000000000),
        (0xbc6f4db3a04cf778, 0x3fcbca374cf8efde),
        (0x3c31e334a32ed081, 0x3f9493391f88f49c),
        (0x3bb77456438b622e, 0x3f4f2aff2cd821b7),
        (0x3b312b847b83fa80, 0x3efb249e459c00fa),
        (0x3b1d3faf77d3ee5b, 0x3e9cd199c39f6d6c),
        (0xbaaf6a6a3d483df8, 0x3e331192e34cb81f),
        (0x3a406e234cb7aede, 0x3dbef6023ba17d1a),
        (0x39dee1ec666e30b5, 0x3d3c8bab78d825e9),
        (0x3910b9821c993936, 0x3ca73bf438398234),
    ];
    let ps_num = f_estrin_polyeval5(
        eval_x.hi,
        f64::from_bits(0x3e9cd199c39f6d6c),
        f64::from_bits(0x3e331192e34cb81f),
        f64::from_bits(0x3dbef6023ba17d1a),
        f64::from_bits(0x3d3c8bab78d825e9),
        f64::from_bits(0x3ca73bf438398234),
    );
    let mut p_num = DoubleDouble::mul_f64_add(eval_x, ps_num, DoubleDouble::from_bit_pair(P[4]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[3]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[2]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[1]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[0]));
    const Q: [(u64, u64); 10] = [
        (0x0000000000000000, 0x3ff0000000000000),
        (0x3c16498103ae0f29, 0xbfa0d722cc1c408a),
        (0x3ba9b44df49b7368, 0x3f41a06d24a9b89a),
        (0x3b43ef4989b8a3ed, 0xbed8363c48ecd98c),
        (0xbaf6197838a8a2ef, 0x3e6830647038f0ac),
        (0x3a96c443c7d52296, 0xbdf257666a799e31),
        (0x3a118e8a97f0df20, 0x3d753ffeb530f0c8),
        (0xb99e90b659ab1bb7, 0xbcf243374f2b7d6c),
        (0x38f647cfef513fc5, 0x3c654740fd120da3),
        (0x386d691099d0e8fc, 0xbbc9c9c826756a76),
    ];
    let ps_den = f_estrin_polyeval5(
        eval_x.hi,
        f64::from_bits(0xbdf257666a799e31),
        f64::from_bits(0x3d753ffeb530f0c8),
        f64::from_bits(0xbcf243374f2b7d6c),
        f64::from_bits(0x3c654740fd120da3),
        f64::from_bits(0xbbc9c9c826756a76),
    );
    let mut p_den = DoubleDouble::mul_f64_add(eval_x, ps_den, DoubleDouble::from_bit_pair(Q[4]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[3]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[2]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[1]));
    p_den = DoubleDouble::mul_add_f64(eval_x, p_den, f64::from_bits(0x3ff0000000000000));
    DoubleDouble::mul_add_f64(DoubleDouble::div(p_num, p_den), eval_x, 1.)
}

// Generated by Wolfram Mathematica:
// I0(x) = 1+(x/2)^2 * R((x/2)^2)
// <<FunctionApproximations`
// ClearAll["Global`*"]
// f[x_]:=(BesselI[0,x]-1)/(x/2)^2
// g[z_]:=f[2 Sqrt[z]]
// {err, approx}=MiniMaxApproximation[g[z],{z,{3.6,7.51},8,8},WorkingPrecision->60]
// poly=Numerator[approx][[1]];
// coeffs=CoefficientList[poly,z];
// TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
// poly=Denominator[approx][[1]];
// coeffs=CoefficientList[poly,z];
// TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
#[cold]
#[inline(never)]
fn eval_small_hard_3p6_to_7p5(x: f64) -> f64 {
    const ONE_OVER_4: f64 = 1. / 4.;
    let eval_x = DoubleDouble::quick_mult_f64(DoubleDouble::from_exact_mult(x, x), ONE_OVER_4);
    const P: [(u64, u64); 10] = [
        (0xbae8195e94c833a1, 0x3ff0000000000000),
        (0xbc6f4db3a04cf778, 0x3fcbca374cf8efde),
        (0x3c31e334a32ed081, 0x3f9493391f88f49c),
        (0x3bb77456438b622e, 0x3f4f2aff2cd821b7),
        (0x3b312b847b83fa80, 0x3efb249e459c00fa),
        (0x3b1d3faf77d3ee5b, 0x3e9cd199c39f6d6c),
        (0xbaaf6a6a3d483df8, 0x3e331192e34cb81f),
        (0x3a406e234cb7aede, 0x3dbef6023ba17d1a),
        (0x39dee1ec666e30b5, 0x3d3c8bab78d825e9),
        (0x3910b9821c993936, 0x3ca73bf438398234),
    ];
    let mut p_num = DoubleDouble::mul_add(
        eval_x,
        DoubleDouble::from_bit_pair(P[9]),
        DoubleDouble::from_bit_pair(P[8]),
    );
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[7]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[6]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[5]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[4]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[3]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[2]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[1]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[0]));
    const Q: [(u64, u64); 10] = [
        (0x0000000000000000, 0x3ff0000000000000),
        (0x3c16498103ae0f29, 0xbfa0d722cc1c408a),
        (0x3ba9b44df49b7368, 0x3f41a06d24a9b89a),
        (0x3b43ef4989b8a3ed, 0xbed8363c48ecd98c),
        (0xbaf6197838a8a2ef, 0x3e6830647038f0ac),
        (0x3a96c443c7d52296, 0xbdf257666a799e31),
        (0x3a118e8a97f0df20, 0x3d753ffeb530f0c8),
        (0xb99e90b659ab1bb7, 0xbcf243374f2b7d6c),
        (0x38f647cfef513fc5, 0x3c654740fd120da3),
        (0x386d691099d0e8fc, 0xbbc9c9c826756a76),
    ];
    let mut p_den = DoubleDouble::mul_add(
        eval_x,
        DoubleDouble::from_bit_pair(Q[9]),
        DoubleDouble::from_bit_pair(Q[8]),
    );
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[7]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[6]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[5]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[4]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[3]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[2]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[1]));
    p_den = DoubleDouble::mul_add_f64(eval_x, p_den, f64::from_bits(0x3ff0000000000000));
    let q = DoubleDouble::mul_add_f64(DoubleDouble::div(p_num, p_den), eval_x, 1.);
    q.to_f64()
}

#[inline]
pub(crate) fn i0_0_to_3p6_exec(x: f64) -> f64 {
    let r = i0_0_to_3p6_dd(x);
    let err = f_fmla(
        r.hi,
        f64::from_bits(0x3c40000000000000), // 2^-59
        f64::from_bits(0x3be0000000000000), // 2^-66
    );
    let ub = r.hi + (r.lo + err);
    let lb = r.hi + (r.lo - err);
    if ub == lb {
        return r.to_f64();
    }
    i0_0_to_3p6_hard(x)
}

// Generated by Wolfram Mathematica:
// <<FunctionApproximations`
// ClearAll["Global`*"]
// f[x_]:=(BesselI[0,x]-1)/(x/2)^2
// g[z_]:=f[2 Sqrt[z]]
// {err, approx}=MiniMaxApproximation[g[z],{z,{0.0000000000001,3.6},7,6},WorkingPrecision->60]
// poly=Numerator[approx][[1]];
// coeffs=CoefficientList[poly,z];
// TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
// poly=Denominator[approx][[1]];
// coeffs=CoefficientList[poly,z];
// TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
#[cold]
#[inline(never)]
fn i0_0_to_3p6_hard(x: f64) -> f64 {
    const ONE_OVER_4: f64 = 1. / 4.;
    let eval_x = DoubleDouble::quick_mult_f64(DoubleDouble::from_exact_mult(x, x), ONE_OVER_4);
    const P: [(u64, u64); 8] = [
        (0xba93dec1e5396e30, 0x3ff0000000000000),
        (0xbc5d3d919a2b891a, 0x3fcb128f49a1f59f),
        (0xbc3c4d80de165459, 0x3f9353508fce278f),
        (0x3be7e0e75c00d411, 0x3f4b760657892e15),
        (0xbb9bc959d02076a4, 0x3ef588ff0ba5872e),
        (0x3b257756675180e4, 0x3e932e320d411521),
        (0xbaca098436a47ec6, 0x3e2285f524a51de0),
        (0x3a0e48fa0331db75, 0x3d9ee6518d82a209),
    ];
    let mut p_num = DoubleDouble::mul_add(
        eval_x,
        DoubleDouble::from_bit_pair(P[7]),
        DoubleDouble::from_bit_pair(P[6]),
    );
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[5]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[4]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[3]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[2]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[1]));
    p_num = DoubleDouble::mul_add(eval_x, p_num, DoubleDouble::from_bit_pair(P[0]));
    const Q: [(u64, u64); 7] = [
        (0x0000000000000000, 0x3ff0000000000000),
        (0x3c26136ec7050a58, 0xbfa3b5c2d9782985),
        (0x3bdf9cd5be66297b, 0x3f478d5c030ea692),
        (0xbb5036196d4b865c, 0xbee1a83b6e8c6fd6),
        (0xbb1a9dafadc75858, 0x3e71ba443893032b),
        (0xba7a719ba9ed7e7f, 0xbdf6e673af3e0c66),
        (0xb9e17740b37a23ec, 0x3d6e2c993fef696f),
    ];
    let mut p_den = DoubleDouble::mul_add(
        eval_x,
        DoubleDouble::from_bit_pair(Q[6]),
        DoubleDouble::from_bit_pair(Q[5]),
    );
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[4]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[3]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[2]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[1]));
    p_den = DoubleDouble::mul_add(eval_x, p_den, DoubleDouble::from_bit_pair(Q[0]));
    let q = DoubleDouble::mul_add_f64(DoubleDouble::div(p_num, p_den), eval_x, 1.);
    q.to_f64()
}

/**
Mid-interval [7.5;9.5] generated by Wolfram:
I0(x)=R(1/x)/sqrt(x)*Exp(x)
```text
<<FunctionApproximations`
ClearAll["Global`*"]
f[x_]:=Sqrt[x] Exp[-x] BesselI[0,x]
g[z_]:=f[1/z]
{err, approx}=MiniMaxApproximation[g[z],{z,{1/9.5,1/7.5},11,11},WorkingPrecision->120]
num=Numerator[approx][[1]];
den=Denominator[approx][[1]];
poly=den;
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50}, ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
```
**/
#[inline]
fn i0_7p5_to_9p5(x: f64) -> f64 {
    let dx = x;
    let recip = DoubleDouble::from_quick_recip(x);

    const P: [(u64, u64); 12] = [
        (0x3c778e3de1f76f48, 0x3fd988450531281b),
        (0xbcb572f6149f389e, 0xc01a786676fb4d3a),
        (0x3cf2f373365347ed, 0x405c0e8405fdb642),
        (0x3d276a94c8f1e627, 0xc0885e4718dfb761),
        (0x3d569f8a993434e2, 0x40b756d52d5fa90c),
        (0xbd6f953f7dd1a223, 0xc0c8818365c47790),
        (0xbd74247967fbf7b2, 0x40e8cf89daf87353),
        (0x3db449add7abb056, 0x41145d3c2d96d159),
        (0xbdc5cc822b71f891, 0xc123694c58fd039b),
        (0x3da2047ac1a6fba8, 0x415462e630bf3e7e),
        (0xbdc2f2c06eda6a95, 0xc14c6984ebdd6792),
        (0xbdf51fa85dafeca5, 0x4166a437c202d27b),
    ];

    let x2 = DoubleDouble::quick_mult(recip, recip);
    let x4 = DoubleDouble::quick_mult(x2, x2);
    let x8 = DoubleDouble::quick_mult(x4, x4);

    let e0 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[1]),
        DoubleDouble::from_bit_pair(P[0]),
    );
    let e1 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[3]),
        DoubleDouble::from_bit_pair(P[2]),
    );
    let e2 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[5]),
        DoubleDouble::from_bit_pair(P[4]),
    );
    let e3 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[7]),
        DoubleDouble::from_bit_pair(P[6]),
    );
    let e4 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[9]),
        DoubleDouble::from_bit_pair(P[8]),
    );
    let e5 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[11]),
        DoubleDouble::from_bit_pair(P[10]),
    );

    let f0 = DoubleDouble::mul_add(x2, e1, e0);
    let f1 = DoubleDouble::mul_add(x2, e3, e2);
    let f2 = DoubleDouble::mul_add(x2, e5, e4);

    let g0 = DoubleDouble::mul_add(x4, f1, f0);

    let p_num = DoubleDouble::mul_add(x8, f2, g0);

    const Q: [(u64, u64); 12] = [
        (0x0000000000000000, 0x3ff0000000000000),
        (0x3cde08e4cbf324d1, 0xc030b67bd69af0ca),
        (0x3cec5e4ee7e77024, 0x4071b54c0f58409c),
        (0xbd340e1131739e2f, 0xc09f140a738b14b3),
        (0x3d607673189d6644, 0x40cdb44bd822add2),
        (0xbd7793a4f1dd74d1, 0xc0e03fe2689b802d),
        (0xbd8415501228a87e, 0x410009beafea72cc),
        (0x3dcecdac2702661f, 0x4128c2073da9a447),
        (0xbdd8386404f3dec5, 0xc1389ec7d7186bf4),
        (0xbe06eb53a3e86436, 0x4168b7a2dc85ed0b),
        (0x3e098e2cefaf8299, 0xc1604f8cf34af02c),
        (0x3e1a5e496b547001, 0x41776b1e0153d1e9),
    ];

    let e0 = DoubleDouble::mul_add_f64(
        recip,
        DoubleDouble::from_bit_pair(Q[1]),
        f64::from_bits(0x3ff0000000000000),
    );
    let e1 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[3]),
        DoubleDouble::from_bit_pair(Q[2]),
    );
    let e2 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[5]),
        DoubleDouble::from_bit_pair(Q[4]),
    );
    let e3 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[7]),
        DoubleDouble::from_bit_pair(Q[6]),
    );
    let e4 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[9]),
        DoubleDouble::from_bit_pair(Q[8]),
    );
    let e5 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[11]),
        DoubleDouble::from_bit_pair(Q[10]),
    );

    let f0 = DoubleDouble::mul_add(x2, e1, e0);
    let f1 = DoubleDouble::mul_add(x2, e3, e2);
    let f2 = DoubleDouble::mul_add(x2, e5, e4);

    let g0 = DoubleDouble::mul_add(x4, f1, f0);

    let p_den = DoubleDouble::mul_add(x8, f2, g0);

    let z = DoubleDouble::div(p_num, p_den);

    let e = i0_exp(dx);
    let r_sqrt = DoubleDouble::from_rsqrt_fast(dx);
    let r = DoubleDouble::quick_mult(z * r_sqrt, e);

    let err = f_fmla(
        r.hi,
        f64::from_bits(0x3bc0000000000000),
        f64::from_bits(0x392bdb8cdadbe111),
    );
    let up = r.hi + (r.lo + err);
    let lb = r.hi + (r.lo - err);
    if up != lb {
        return i0_7p5_to_9p5_hard(x);
    }
    r.to_f64()
}

/**
Mid-interval [7.5;9.5] generated by Wolfram Mathematica:
I0(x)=R(1/x)/sqrt(x)*Exp(x)
Polynomial generated by Wolfram Mathematica:
```text
<<FunctionApproximations`
ClearAll["Global`*"]
f[x_]:=Sqrt[x] Exp[-x] BesselI[0,x]
g[z_]:=f[1/z]
{err,approx}=MiniMaxApproximation[g[z],{z,{1/9.5,1/7.5},13,13},WorkingPrecision->120]
poly=Numerator[approx][[1]];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
poly=Denominator[approx][[1]];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
```
**/
#[cold]
#[inline(never)]
fn i0_7p5_to_9p5_hard(x: f64) -> f64 {
    static P: [DyadicFloat128; 14] = [
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -129,
            mantissa: 0xcc422a04_45cde144_75a3800b_45c38460_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -124,
            mantissa: 0xada66144_fcccc1a3_036f76b2_cabd6281_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -120,
            mantissa: 0xeabdda02_fa201d98_10e58d1f_7eb62bd7_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -116,
            mantissa: 0xbbfd3297_6f88a7df_5924587b_d5bdcdb8_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -113,
            mantissa: 0xfca29453_efe393bf_1651627b_7d543875_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -110,
            mantissa: 0xee7c7220_bbbd248e_bb6adac6_f9a5ce95_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -107,
            mantissa: 0xc07455dd_830ba705_414408c6_06732a5a_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -105,
            mantissa: 0xe2247793_b50cd0f0_80e8981d_933f75da_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -103,
            mantissa: 0xe14a9831_82582a0b_dd27e8b6_4ed9aac2_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -101,
            mantissa: 0xa3b2ae2f_5b64f37e_c1538435_34f02faf_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -100,
            mantissa: 0xbab73503_5b7e38d9_bbe4a84b_9007c6e8_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -99,
            mantissa: 0xa68911fc_5d87bbe7_0d4fe854_5c681ac5_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -99,
            mantissa: 0x9e997222_55ef4045_fa9f311d_57d082a2_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -99,
            mantissa: 0xbe93656a_b0a4f32d_3ebbfdeb_b1cbb839_u128,
        },
    ];
    static Q: [DyadicFloat128; 14] = [
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -127,
            mantissa: 0x80000000_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -123,
            mantissa: 0xdaa34a7e_861dddff_a0642080_cd83dd65_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -118,
            mantissa: 0x93f05740_f4758772_bb9992f9_91e72795_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -115,
            mantissa: 0xeddcb810_054c9aab_fa7e4214_d59d18b0_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -111,
            mantissa: 0xa0180fcd_831ff6c0_ac2b8f02_37f3cfd1_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -108,
            mantissa: 0x97d25106_3b66907e_90b4f786_26daa0bb_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -106,
            mantissa: 0xf595ce38_aac16c11_001b874a_99603b45_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -103,
            mantissa: 0x912b3715_4aca68f6_5821c2ed_43d77111_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -101,
            mantissa: 0x90f97141_b896e2b6_38b87354_8945a43c_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -100,
            mantissa: 0xd3e5f967_89097d6b_3a3060fe_852ff580_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -99,
            mantissa: 0xf0d6de35_939da009_9ced21fd_48af7281_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -98,
            mantissa: 0xd2a0b183_6ac613b2_6745ce1d_8ed1c323_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -98,
            mantissa: 0xbb9c089a_b7e939a2_732b3fb5_2e66cd77_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -98,
            mantissa: 0xcb2107c3_736bef81_609718c0_ba82cd8e_u128,
        },
    ];

    let recip = DyadicFloat128::accurate_reciprocal(x);

    let mut p_num = P[13];
    for i in (0..13).rev() {
        p_num = recip * p_num + P[i];
    }
    let mut p_den = Q[13];
    for i in (0..13).rev() {
        p_den = recip * p_den + Q[i];
    }
    let z = p_num * p_den.reciprocal();

    let r_sqrt = bessel_rsqrt_hard(x, recip);
    let f_exp = rational128_exp(x);
    (z * r_sqrt * f_exp).fast_as_f64()
}

#[inline(always)]
fn exp_poly(z: f64) -> DoubleDouble {
    /* The following is a degree-4 polynomial generated by Sollya for exp(x)
    over [-2^-12.905,2^-12.905]
    with absolute error < 2^-74.34 (see sollya/Q_1.sollya). */
    const Q_1: [u64; 5] = [
        0x3ff0000000000000,
        0x3ff0000000000000,
        0x3fe0000000000000,
        0x3fc5555555997996,
        0x3fa5555555849d8d,
    ];
    let mut q = dd_fmla(f64::from_bits(Q_1[4]), z, f64::from_bits(Q_1[3]));
    q = dd_fmla(q, z, f64::from_bits(Q_1[2]));
    let h0 = dd_fmla(q, z, f64::from_bits(Q_1[1]));

    let v1 = DoubleDouble::from_exact_mult(z, h0);
    DoubleDouble::f64_add(f64::from_bits(Q_1[0]), v1)
}

#[inline]
pub(crate) fn i0_exp(r: f64) -> DoubleDouble {
    const INVLOG2: f64 = f64::from_bits(0x40b71547652b82fe);

    let k = (r * INVLOG2).round();

    const L2: DoubleDouble = DoubleDouble::new(
        f64::from_bits(0x3d0718432a1b0e26),
        f64::from_bits(0x3f262e42ff000000),
    );

    let zh = f_fmla(L2.lo, k, f_fmla(-L2.hi, k, r));

    let bk = k as i64;
    let mk = (bk >> 12) + 0x3ff;
    let i2 = (bk >> 6) & 0x3f;
    let i1 = bk & 0x3f;

    let t0 = DoubleDouble::from_bit_pair(EXP_REDUCE_T0[i2 as usize]);
    let t1 = DoubleDouble::from_bit_pair(EXP_REDUCE_T1[i1 as usize]);
    let mut de = DoubleDouble::quick_mult(t1, t0);
    let q = exp_poly(zh);
    de = DoubleDouble::quick_mult(de, q);

    let mut du = (mk as u64).wrapping_shl(52);
    du = f64::from_bits(du).to_bits();
    DoubleDouble::quick_mult_f64(de, f64::from_bits(du))
}

/**
I0(x)=R(1/x)*Exp(x)/sqrt(x)
Generated in Wolfram:
```text
<<FunctionApproximations`
ClearAll["Global`*"]
f[x_]:=Sqrt[x] Exp[-x] BesselI[0,x]
g[z_]:=f[1/z]
{err,approx, err1}=MiniMaxApproximation[g[z],{z,{1/709.3,1/9.5},11,11},WorkingPrecision->120]
poly=Numerator[approx];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
poly=Denominator[approx];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]]
```
**/
#[inline]
fn i0_asympt(x: f64) -> f64 {
    let dx = x;
    let recip = DoubleDouble::from_quick_recip(x);
    const P: [(u64, u64); 12] = [
        (0xbc7ca19c5d824c54, 0x3fd9884533d43651),
        (0x3cca32eb734e010e, 0xc03b7ca35caf42eb),
        (0x3d03af8238d6f25e, 0x408b92cfcaa7070f),
        (0xbd7a8ff7fdebed70, 0xc0d0a3be432cce93),
        (0xbdababdb579bb076, 0x410a77dc51f1804d),
        (0x3dc5e4e3c972832a, 0xc13cb0be2f74839c),
        (0x3e01076f9b102e38, 0x41653b064cc61661),
        (0xbe2157e700d445f4, 0xc184e1b076927460),
        (0xbdfa4577156dde56, 0x41999e9653f9dc5f),
        (0xbe47aa238a739ffe, 0xc1a130f6ded40c00),
        (0xbe331b560b6fbf4a, 0x419317f11e674cae),
        (0xbe0765596077d1e3, 0xc16024404db59d3f),
    ];

    let x2 = DoubleDouble::quick_mult(recip, recip);
    let x4 = DoubleDouble::quick_mult(x2, x2);
    let x8 = DoubleDouble::quick_mult(x4, x4);

    let e0 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[1]),
        DoubleDouble::from_bit_pair(P[0]),
    );
    let e1 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[3]),
        DoubleDouble::from_bit_pair(P[2]),
    );
    let e2 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[5]),
        DoubleDouble::from_bit_pair(P[4]),
    );
    let e3 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[7]),
        DoubleDouble::from_bit_pair(P[6]),
    );
    let e4 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[9]),
        DoubleDouble::from_bit_pair(P[8]),
    );
    let e5 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(P[11]),
        DoubleDouble::from_bit_pair(P[10]),
    );

    let f0 = DoubleDouble::mul_add(x2, e1, e0);
    let f1 = DoubleDouble::mul_add(x2, e3, e2);
    let f2 = DoubleDouble::mul_add(x2, e5, e4);

    let g0 = DoubleDouble::mul_add(x4, f1, f0);

    let p_num = DoubleDouble::mul_add(x8, f2, g0);

    const Q: [(u64, u64); 12] = [
        (0x0000000000000000, 0x3ff0000000000000),
        (0xbcf687703e843d07, 0xc051418f1c4dd0b9),
        (0x3d468ab92cb87a0b, 0x40a15891d823e48d),
        (0x3d8bfc17e5183376, 0xc0e4fce40dd82796),
        (0xbdccbbcc2ecf8d4c, 0x4120beef869c61ec),
        (0xbdf42170b4d5d150, 0xc1523ad18834a7ed),
        (0xbe0eaa32f405afd4, 0x417b24ec57a8f480),
        (0x3e3ec900705e7252, 0xc19af2a91d23d62e),
        (0x3e3e220e274fa46b, 0x41b0cb905cc99ff5),
        (0xbe46c6c61dee11f6, 0xc1b7452e50518520),
        (0x3e3ed0fc063187bf, 0x41ac1772d1749896),
        (0xbe11c578f04f4be1, 0xc180feb5b2ca47cb),
    ];

    let e0 = DoubleDouble::mul_add_f64(
        recip,
        DoubleDouble::from_bit_pair(Q[1]),
        f64::from_bits(0x3ff0000000000000),
    );
    let e1 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[3]),
        DoubleDouble::from_bit_pair(Q[2]),
    );
    let e2 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[5]),
        DoubleDouble::from_bit_pair(Q[4]),
    );
    let e3 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[7]),
        DoubleDouble::from_bit_pair(Q[6]),
    );
    let e4 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[9]),
        DoubleDouble::from_bit_pair(Q[8]),
    );
    let e5 = DoubleDouble::mul_add(
        recip,
        DoubleDouble::from_bit_pair(Q[11]),
        DoubleDouble::from_bit_pair(Q[10]),
    );

    let f0 = DoubleDouble::mul_add(x2, e1, e0);
    let f1 = DoubleDouble::mul_add(x2, e3, e2);
    let f2 = DoubleDouble::mul_add(x2, e5, e4);

    let g0 = DoubleDouble::mul_add(x4, f1, f0);

    let p_den = DoubleDouble::mul_add(x8, f2, g0);

    let z = DoubleDouble::div(p_num, p_den);

    let mut e = i0_exp(dx * 0.5);
    e = DoubleDouble::from_exact_add(e.hi, e.lo);
    let r_sqrt = DoubleDouble::from_rsqrt_fast(dx);
    let r = DoubleDouble::quick_mult(z * r_sqrt * e, e);
    let err = f_fmla(
        r.hi,
        f64::from_bits(0x3c40000000000000), // 2^-59
        f64::from_bits(0x3be0000000000000), // 2^-65
    );
    let up = r.hi + (r.lo + err);
    let lb = r.hi + (r.lo - err);
    if up != lb {
        return i0_asympt_hard(x);
    }
    lb
}

#[inline]
pub(crate) fn bessel_rsqrt_hard(x: f64, recip: DyadicFloat128) -> DyadicFloat128 {
    let r = DyadicFloat128::new_from_f64(x.sqrt()) * recip;
    let fx = DyadicFloat128::new_from_f64(x);
    let rx = r * fx;
    let drx = r * fx - rx;
    const M_ONE: DyadicFloat128 = DyadicFloat128 {
        sign: DyadicSign::Neg,
        exponent: -127,
        mantissa: 0x80000000_00000000_00000000_00000000_u128,
    };
    let h = r * rx + M_ONE + r * drx;
    let mut ddr = r;
    ddr.exponent -= 1; // ddr * 0.5
    let dr = ddr * h;
    r - dr
}

/**
I0(x)=R(1/x)*Exp(x)/sqrt(x)
Generated in Wolfram:
```text
<<FunctionApproximations`
ClearAll["Global`*"]
f[x_]:=Sqrt[x] Exp[-x] BesselI[0,x]
g[z_]:=f[1/z]
{err,approx, err1}=MiniMaxApproximation[g[z],{z,{1/709.3,1/9.5},15,15},WorkingPrecision->120]
poly=Numerator[approx];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
poly=Denominator[approx];
coeffs=CoefficientList[poly,z];
TableForm[Table[Row[{"'",NumberForm[coeffs[[i+1]],{50,50},ExponentFunction->(Null&)],"',"}],{i,0,Length[coeffs]-1}]]
```
**/
#[cold]
#[inline(never)]
fn i0_asympt_hard(x: f64) -> f64 {
    static P: [DyadicFloat128; 16] = [
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -129,
            mantissa: 0xcc42299e_a1b28468_7e5aad4a_70b749c4_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -122,
            mantissa: 0xabb4209d_ca11bdaa_186bef7f_390e6b77_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -116,
            mantissa: 0x8a2725e2_4749db25_625ad1f2_12a2a16c_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -111,
            mantissa: 0x8b4c2cd4_9e5d0c8b_c9be4d3e_781bb676_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -107,
            mantissa: 0xc33fad7c_40599f7d_713e3081_6b5ad791_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -103,
            mantissa: 0xc81da271_b623ba88_0be032b5_827d92fa_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -99,
            mantissa: 0x99ec4975_b6aa7cae_7692a287_ed8ae47c_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -96,
            mantissa: 0xb3aa4745_fc2dd441_2dbd3e3c_d4539687_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -93,
            mantissa: 0x9f14edc2_6882afca_29d2a067_dc459729_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -91,
            mantissa: 0xd35c4d01_78d8cec6_fc8ae0ee_834da837_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -89,
            mantissa: 0xcdc529c7_6e082342_faad3073_07a9b61f_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -87,
            mantissa: 0x8ccac88f_2598c8a6_423b1f42_63591cb9_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -87,
            mantissa: 0xfc044cfb_a20f0885_93d58660_17819ed5_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -86,
            mantissa: 0x813a700c_74d23f52_f81b179d_7ff0da9f_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -88,
            mantissa: 0xe6c43da4_297216bf_bdd987cb_636906cf_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -91,
            mantissa: 0xa4998323_649c3cf2_64477869_3d2c6afd_u128,
        },
    ];
    static Q: [DyadicFloat128; 16] = [
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -127,
            mantissa: 0x80000000_00000000_00000000_00000000_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -121,
            mantissa: 0xd772d5fd_a7077638_6e007274_d83b013c_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -115,
            mantissa: 0xad914ef0_451ced2e_515657ef_fc7eeb53_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -110,
            mantissa: 0xaf41180c_dffe96e5_f192fa40_0b1bff1e_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -106,
            mantissa: 0xf60dc728_241f71fd_5b93e653_ccbedace_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -102,
            mantissa: 0xfcaefef9_39cf96e7_3cb75a98_da5e9761_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -98,
            mantissa: 0xc2d2c837_5789587a_13ef38c6_a24c3413_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -95,
            mantissa: 0xe41725c3_51d14486_a650044e_e8588f7b_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -92,
            mantissa: 0xcabeed9b_5e2e888d_81d32b11_d289a624_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -89,
            mantissa: 0x8764876d_11ad6607_8a8d5382_3ffe82d9_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -87,
            mantissa: 0x84c9f9e5_6a5f5034_ad2c8512_16cb1ba1_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -86,
            mantissa: 0xb7c1d143_a15d8aab_03a7fa3e_b7d07a36_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -85,
            mantissa: 0xa78f8257_4658040f_7a1ad39c_91ea9483_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -85,
            mantissa: 0xb231e0ca_b729a404_44c38f52_be208507_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Pos,
            exponent: -86,
            mantissa: 0xae317981_42349081_8bc68b28_f69b8e49_u128,
        },
        DyadicFloat128 {
            sign: DyadicSign::Neg,
            exponent: -89,
            mantissa: 0xb451abd3_5cd79c6d_7e578164_32f16da1_u128,
        },
    ];

    let recip = DyadicFloat128::accurate_reciprocal(x);

    let mut p_num = P[15];
    for i in (0..15).rev() {
        p_num = recip * p_num + P[i];
    }

    let mut p_den = Q[15];
    for i in (0..15).rev() {
        p_den = recip * p_den + Q[i];
    }

    let z = p_num * p_den.reciprocal();

    let r_sqrt = bessel_rsqrt_hard(x, recip);
    let f_exp = rational128_exp(x);
    (z * r_sqrt * f_exp).fast_as_f64()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_i0() {
        assert_eq!(f_i0(9.500000000005492,), 1753.4809905364318);
        assert!(f_i0(f64::NAN).is_nan());
        assert_eq!(f_i0(f64::INFINITY), f64::INFINITY);
        assert_eq!(f_i0(f64::NEG_INFINITY), f64::INFINITY);
        assert_eq!(f_i0(7.500000000788034), 268.1613117118702);
        assert_eq!(f_i0(715.), f64::INFINITY);
        assert_eq!(f_i0(12.), 18948.925349296307);
        assert_eq!(f_i0(16.), 893446.227920105);
        assert_eq!(f_i0(18.432), 9463892.87209841);
        assert_eq!(f_i0(26.432), 23507752424.350075);
        assert_eq!(f_i0(0.2), 1.010025027795146);
        assert_eq!(f_i0(7.5), 268.16131151518937);
        assert_eq!(f_i0(-1.5), 1.646723189772891);
    }
}
