use crate::constants::*;
use crate::error::SolMathError;
use crate::arithmetic::{fp_mul_i, fp_mul_i_round, fp_div_i, fp_div_i_round, fp_sqrt};
use crate::transcendental::{exp_fixed_i, ln_fixed_i};
pub fn norm_pdf(x: i128) -> Result<i128, SolMathError> {
let x_sq = fp_mul_i(x, x)?;
let neg_half_x_sq = -(x_sq / 2);
if neg_half_x_sq < -40 * SCALE_I {
return Ok(0);
}
let exp_term = match exp_fixed_i(neg_half_x_sq) {
Ok(v) => v,
Err(_) => return Ok(0), };
fp_mul_i(INV_SQRT_2PI, exp_term)
}
#[inline]
pub(crate) fn horner_11_round(c: &[i128; 12], t: i128) -> Result<i128, SolMathError> {
let mut r = c[11];
r = fp_mul_i_round(r, t)? + c[10];
r = fp_mul_i_round(r, t)? + c[9];
r = fp_mul_i_round(r, t)? + c[8];
r = fp_mul_i_round(r, t)? + c[7];
r = fp_mul_i_round(r, t)? + c[6];
r = fp_mul_i_round(r, t)? + c[5];
r = fp_mul_i_round(r, t)? + c[4];
r = fp_mul_i_round(r, t)? + c[3];
r = fp_mul_i_round(r, t)? + c[2];
r = fp_mul_i_round(r, t)? + c[1];
r = fp_mul_i_round(r, t)? + c[0];
Ok(r)
}
#[inline]
pub(crate) fn poly_map_t_round(ax: i128, mid: i128, hw: i128) -> Result<i128, SolMathError> {
let num = (ax - mid).checked_mul(SCALE_I).ok_or(SolMathError::Overflow)?;
Ok(if num >= 0 { (num + hw / 2) / hw } else { (num - hw / 2) / hw })
}
#[inline]
pub(crate) fn mills_ratio_cf6(x: i128) -> Result<i128, SolMathError> {
let mut r = 0i128;
for k in (1..=6).rev() {
r = fp_div_i_round(k * SCALE_I, x + r)?;
}
fp_div_i_round(SCALE_I, x + r)
}
pub(crate) fn norm_cdf_tail(x_abs: i128) -> Result<i128, SolMathError> {
let x_sq_half = fp_mul_i_round(x_abs, x_abs)? / 2;
if x_sq_half > 40 * SCALE_I {
return Ok(SCALE_I);
}
let exp_val = exp_fixed_i(-x_sq_half)?;
let pdf = fp_mul_i_round(INV_SQRT_2PI, exp_val)?;
let mills = mills_ratio_cf6(x_abs)?;
let tail = fp_mul_i_round(pdf, mills)?;
Ok((SCALE_I - tail).clamp(0, SCALE_I))
}
pub fn norm_cdf_poly(x: i128) -> Result<i128, SolMathError> {
if x < -8 * SCALE_I {
return Ok(0);
}
if x > 8 * SCALE_I {
return Ok(SCALE_I);
}
if x == 0 {
return Ok(SCALE_I / 2);
}
let ax = x.abs();
let cdf_pos = if ax <= POLY_V2_I0_HI {
horner_11_round(&POLY_V2_I0, poly_map_t_round(ax, POLY_V2_I0_MID, POLY_V2_I0_HW)?)?
} else if ax <= POLY_V2_I1_HI {
horner_11_round(&POLY_V2_I1, poly_map_t_round(ax, POLY_V2_I1_MID, POLY_V2_I1_HW)?)?
} else if ax <= POLY_V2_I2_HI {
horner_11_round(&POLY_V2_I2, poly_map_t_round(ax, POLY_V2_I2_MID, POLY_V2_I2_HW)?)?
} else if ax <= POLY_V2_I3_HI {
horner_11_round(&POLY_V2_I3, poly_map_t_round(ax, POLY_V2_I3_MID, POLY_V2_I3_HW)?)?
} else if ax <= POLY_V2_I4_HI {
horner_11_round(&POLY_V2_I4, poly_map_t_round(ax, POLY_V2_I4_MID, POLY_V2_I4_HW)?)?
} else if ax <= 5 * SCALE_I {
horner_11_round(&POLY_V2_I5, poly_map_t_round(ax, POLY_V2_I5_MID, POLY_V2_I5_HW)?)?
} else {
norm_cdf_tail(ax)?
};
let cdf_pos = cdf_pos.clamp(0, SCALE_I);
Ok(if x >= 0 {
cdf_pos
} else {
SCALE_I - cdf_pos
})
}
pub fn norm_cdf_and_pdf(x: i128) -> Result<(i128, i128), SolMathError> {
Ok((norm_cdf_poly(x)?, norm_pdf(x)?))
}
pub(crate) fn norm_cdf_and_pdf_bs_guarded(x: i128) -> Result<(i128, i128), SolMathError> {
if x <= -8 * SCALE_I {
return Ok((0, 0));
}
if x >= 8 * SCALE_I {
return Ok((SCALE_I, 0));
}
Ok((norm_cdf_poly(x)?, norm_pdf(x)?))
}
#[inline]
fn horner_7_round(c: &[i128; 8], r: i128) -> Result<i128, SolMathError> {
let mut acc = c[7];
acc = fp_mul_i_round(acc, r)? + c[6];
acc = fp_mul_i_round(acc, r)? + c[5];
acc = fp_mul_i_round(acc, r)? + c[4];
acc = fp_mul_i_round(acc, r)? + c[3];
acc = fp_mul_i_round(acc, r)? + c[2];
acc = fp_mul_i_round(acc, r)? + c[1];
acc = fp_mul_i_round(acc, r)? + c[0];
Ok(acc)
}
#[inline]
fn horner_7_den_round(c: &[i128; 7], r: i128) -> Result<i128, SolMathError> {
let mut acc = c[6];
acc = fp_mul_i_round(acc, r)? + c[5];
acc = fp_mul_i_round(acc, r)? + c[4];
acc = fp_mul_i_round(acc, r)? + c[3];
acc = fp_mul_i_round(acc, r)? + c[2];
acc = fp_mul_i_round(acc, r)? + c[1];
acc = fp_mul_i_round(acc, r)? + c[0];
Ok(fp_mul_i_round(acc, r)? + SCALE_I)
}
pub fn inverse_norm_cdf(p: i128) -> Result<i128, SolMathError> {
if p <= 0 || p >= SCALE_I {
return Err(SolMathError::DomainError);
}
let q = p - SCALE_I / 2;
if q.abs() <= AS241_SPLIT1 {
let r = AS241_CONST1 - fp_mul_i(q, q)?;
let num = horner_7_round(&AS241_A, r)?;
let den = horner_7_den_round(&AS241_B, r)?;
fp_div_i(fp_mul_i_round(q, num)?, den)
} else {
let tail = if q < 0 { p } else { SCALE_I - p };
let ln_tail = ln_fixed_i(tail as u128)?;
let neg_ln = -ln_tail; let r = fp_sqrt(neg_ln as u128)? as i128;
let ret = if r < AS241_SPLIT_2B {
let r_adj = r - AS241_CONST2;
let num = horner_7_round(&AS241_C, r_adj)?;
let den = horner_7_den_round(&AS241_D, r_adj)?;
fp_div_i(num, den)?
} else if r < AS241_SPLIT_2C {
let r_adj = r - AS241_CENTER_2B;
let num = horner_7_round(&AS241_G, r_adj)?;
let den = horner_7_den_round(&AS241_H, r_adj)?;
fp_div_i(num, den)?
} else if r < AS241_SPLIT2 {
let r_adj = r - AS241_CENTER_2C;
let num = horner_7_round(&AS241_I, r_adj)?;
let den = horner_7_den_round(&AS241_J, r_adj)?;
fp_div_i(num, den)?
} else {
let r_adj = r - AS241_SPLIT2;
let num = horner_7_round(&AS241_E, r_adj)?;
let den = horner_7_den_round(&AS241_F, r_adj)?;
fp_div_i(num, den)?
};
if q < 0 { Ok(-ret) } else { Ok(ret) }
}
}