r2rs-nmath 0.1.1

Statistics programming for Rust based on R's nmath package
Documentation
// Translation of nmath's qnt
//
// Copyright (C) 2020 neek-sss
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use num_traits::Float;
use strafe_type::{FloatConstraint, LogProbability64, Probability64, Rational64, Real64};

use crate::{
    distribution::{
        norm::{log_qnorm, qnorm},
        t::{log_qt, pnt, qt},
    },
    traits::DPQ,
};

pub fn qnt<P: Into<Probability64>, RA: Into<Rational64>, RE: Into<Real64>>(
    p: P,
    df: RA,
    ncp: RE,
    lower_tail: bool,
) -> Real64 {
    let p = p.into().unwrap();
    qnt_inner(p, df, ncp, lower_tail, false)
}

pub fn log_qnt<LP: Into<LogProbability64>, RA: Into<Rational64>, RE: Into<Real64>>(
    p: LP,
    df: RA,
    ncp: RE,
    lower_tail: bool,
) -> Real64 {
    let p = p.into().unwrap();
    qnt_inner(p, df, ncp, lower_tail, true)
}

fn qnt_inner<RA: Into<Rational64>, RE: Into<Real64>>(
    mut p: f64,
    df: RA,
    ncp: RE,
    lower_tail: bool,
    log: bool,
) -> Real64 {
    let df = df.into().unwrap();
    let ncp = ncp.into().unwrap();

    static accu: f64 = 1e-13; /* must be > accu */
    static Eps: f64 = 1e-11;

    let mut ux = 0.0;
    let mut lx = 0.0;
    let mut nx = 0.0;
    let mut pp = 0.0;

    if ncp == 0.0 && df >= 1.0 {
        return if log {
            log_qt(p, df, lower_tail)
        } else {
            qt(p, df, lower_tail)
        };
    }

    if let Some(ret) = p.q_p01_boundaries(f64::NEG_INFINITY, f64::infinity(), lower_tail, log) {
        return ret.into();
    }

    if !df.is_finite() {
        // df = Inf ==> limit N(ncp,1)
        return if log {
            log_qnorm(p, ncp, 1.0, lower_tail)
        } else {
            qnorm(p, ncp, 1.0, lower_tail)
        };
    }

    p = p.dt_qiv(lower_tail, log);

    /* Invert pnt(.) :
     * 1.0 finding an upper and lower bound */
    if p > 1.0 - 2.2204460492503131e-16 {
        return f64::infinity().into();
    }
    pp = (p * (1.0 + Eps)).min(1.0 - 2.2204460492503131e-16);
    ux = ncp.max(1.0);
    while ux < f64::MAX && pnt(ux, df, ncp, true).unwrap() < pp {
        ux *= 2.0
    }
    pp = p * (1.0 - Eps);
    lx = (-ncp).min(-1.0);
    while lx > -f64::MAX && pnt(lx, df, ncp, true).unwrap() > pp {
        lx *= 2.0
    }

    /* 2. interval (lx,ux)  halving : */
    loop {
        nx = 0.5 * (lx + ux); // could be zero
        if pnt(nx, df, ncp, true).unwrap() > p {
            ux = nx
        } else {
            lx = nx
        }
        if !(ux - lx > accu * lx.abs().max(ux.abs())) {
            break;
        }
    }

    (0.5 * (lx + ux)).into()
}