use crate::constants::*;
use crate::double_word::DoubleWord;
use crate::error::SolMathError;
use crate::arithmetic::{fp_mul, fp_mul_i, fp_sqrt, isqrt_u128};
use crate::overflow::{checked_mul_div_i, checked_mul_div_u};
use crate::transcendental::exp_fixed_i;
#[inline(always)]
pub fn fp_mul_hp_u(a: u128, b: u128) -> Result<u128, SolMathError> {
let hi_a = a / SCALE_HP_U;
let lo_a = a % SCALE_HP_U;
let hi_b = b / SCALE_HP_U;
let lo_b = b % SCALE_HP_U;
let hh = hi_a.checked_mul(hi_b).ok_or(SolMathError::Overflow)?
.checked_mul(SCALE_HP_U).ok_or(SolMathError::Overflow)?;
let hl = hi_a.checked_mul(lo_b).ok_or(SolMathError::Overflow)?;
let lh = lo_a.checked_mul(hi_b).ok_or(SolMathError::Overflow)?;
let ll = lo_a.checked_mul(lo_b).ok_or(SolMathError::Overflow)?
.checked_add(SCALE_HP_U / 2).ok_or(SolMathError::Overflow)? / SCALE_HP_U;
hh.checked_add(hl).ok_or(SolMathError::Overflow)?
.checked_add(lh).ok_or(SolMathError::Overflow)?
.checked_add(ll).ok_or(SolMathError::Overflow)
}
#[inline(always)]
pub fn fp_mul_hp_i(a: i128, b: i128) -> Result<i128, SolMathError> {
let neg = (a < 0) != (b < 0);
let raw = fp_mul_hp_u(a.unsigned_abs(), b.unsigned_abs())?;
if neg {
if raw == (1u128 << 127) {
Ok(i128::MIN)
} else if raw < (1u128 << 127) {
Ok(-(raw as i128))
} else {
Err(SolMathError::Overflow)
}
} else if raw <= i128::MAX as u128 {
Ok(raw as i128)
} else {
Err(SolMathError::Overflow)
}
}
#[inline]
pub(crate) fn fp_mul_hp_fast(a: i128, b: i128) -> i128 {
let p = a * b;
if p >= 0 {
(p + SCALE_HP / 2) / SCALE_HP
} else {
(p - SCALE_HP / 2) / SCALE_HP
}
}
#[inline]
pub fn fp_div_hp_safe(a: i128, b: i128) -> Result<i128, SolMathError> {
if b == 0 {
return Err(SolMathError::DivisionByZero);
}
let q = a / b;
let r = a % b;
let q_scaled = q.checked_mul(SCALE_HP).ok_or(SolMathError::Overflow)?;
let r_scaled = match r.checked_mul(SCALE_HP) {
Some(v) => v / b,
None => checked_mul_div_i(r, SCALE_HP, b)?,
};
q_scaled.checked_add(r_scaled).ok_or(SolMathError::Overflow)
}
#[inline]
pub(crate) fn upscale_std_to_hp(x: u128) -> Result<i128, SolMathError> {
let max_input = (i128::MAX / HP_TO_STD) as u128;
if x > max_input {
return Err(SolMathError::Overflow);
}
Ok(x as i128 * HP_TO_STD)
}
#[inline]
pub(crate) fn downscale_hp_to_std(x: i128) -> u128 {
if x <= 0 {
0
} else {
((x + HP_TO_STD / 2) / HP_TO_STD) as u128
}
}
#[inline]
pub(crate) fn downscale_hp_to_std_i(x: i128) -> i128 {
if x >= 0 {
(x + HP_TO_STD / 2) / HP_TO_STD
} else {
(x - HP_TO_STD / 2) / HP_TO_STD
}
}
pub fn ln_fixed_hp(x: i128) -> Result<i128, SolMathError> {
if x <= 0 {
return Err(SolMathError::DomainError);
}
let mut m = x as u128;
let mut k: i32 = 0;
while m < SCALE_HP_U {
m = m.checked_mul(2).ok_or(SolMathError::Overflow)?;
k -= 1;
}
while m >= 2 * SCALE_HP_U {
m /= 2;
k += 1;
}
let t_num = m as i128 - SCALE_HP;
let t_den = m as i128 + SCALE_HP;
let p = t_num * SCALE_HP;
let q = p / t_den;
let r = p % t_den;
let half_den = t_den / 2;
let (t, t_lo) = if r >= half_den {
(q + 1, (r - t_den) * SCALE_HP / t_den)
} else {
(q, r * SCALE_HP / t_den)
};
let u = fp_mul_hp_fast(t, t);
let p_dw = horner_compensated_hp_dw(&LN_REMEZ_HP_COEFFS, u)?;
let tp_dw = fp_mul_hp_fast_dw(t, p_dw.hi());
let poly_lo_corr = t * p_dw.lo() / SCALE_HP;
let t_lo_corr = t_lo * p_dw.hi() / SCALE_HP;
let total_lo_2 = 2 * (tp_dw.lo() + poly_lo_corr + t_lo_corr);
let sub_ulp_correction = if total_lo_2 >= 0 {
(total_lo_2 + SCALE_HP / 2) / SCALE_HP
} else {
(total_lo_2 - SCALE_HP / 2) / SCALE_HP
};
let series_result = 2 * tp_dw.hi() + sub_ulp_correction;
let k_i = k as i128;
let raw = k_i * LN2_HP_LO;
let ln2_correction = if raw >= 0 {
(raw + SCALE_HP / 2) / SCALE_HP
} else {
(raw - SCALE_HP / 2) / SCALE_HP
};
Ok(series_result + k_i * LN2_HP + ln2_correction)
}
pub fn exp_fixed_hp(x: i128) -> Result<i128, SolMathError> {
let max_x = 40 * SCALE_HP;
if x <= -max_x { return Ok(0); }
if x >= max_x { return Err(SolMathError::Overflow); }
if x == 0 { return Ok(SCALE_HP); }
let mut k = x / LN2_HP;
let ln2_correction = {
let raw = k * LN2_HP_LO;
if raw >= 0 { (raw + SCALE_HP / 2) / SCALE_HP } else { (raw - SCALE_HP / 2) / SCALE_HP }
};
let mut r = x - k * LN2_HP - ln2_correction;
if r > HALF_LN2_HP { k += 1; r -= LN2_HP; }
else if r < -HALF_LN2_HP { k -= 1; r += LN2_HP; }
let xx = fp_mul_hp_fast(r, r);
let poly = fp_mul_hp_fast(xx, EXP_REMEZ_HP_P5) + EXP_REMEZ_HP_P4;
let poly = fp_mul_hp_fast(xx, poly) + EXP_REMEZ_HP_P3;
let poly = fp_mul_hp_fast(xx, poly) + EXP_REMEZ_HP_P2;
let poly = fp_mul_hp_fast(xx, poly) + EXP_REMEZ_HP_P1;
let c = r - fp_mul_hp_fast(poly, xx);
let rc = fp_mul_hp_fast(r, c);
let sum = SCALE_HP + r + fp_div_hp_safe(rc, 2 * SCALE_HP - c)?;
if k >= 0 {
sum.checked_shl(k as u32).ok_or(SolMathError::Overflow)
} else {
Ok(sum >> (-k) as u32)
}
}
pub fn pow_fixed_hp(base: u128, exponent: u128) -> Result<u128, SolMathError> {
if base == 0 && exponent == 0 {
return Err(SolMathError::DomainError);
}
if exponent == 0 {
return Ok(SCALE);
}
if base == 0 {
return Ok(0);
}
if exponent == SCALE {
return Ok(base);
}
if base == SCALE {
return Ok(SCALE);
}
if exponent == 2 * SCALE {
return fp_mul(base, base);
}
if exponent == SCALE / 2 {
return fp_sqrt(base);
}
let base_hp = upscale_std_to_hp(base)?;
let exp_hp = upscale_std_to_hp(exponent)?;
let ln_base = ln_fixed_hp(base_hp)?;
let product = fp_mul_hp_i(exp_hp, ln_base)?;
if product.abs() < 39 * SCALE_HP {
let result_hp = exp_fixed_hp(product)?;
return Ok(downscale_hp_to_std(result_hp));
}
let n = (exponent / SCALE) as u32;
let frac_std = exponent % SCALE;
let mut int_result: u128 = SCALE;
let mut pow_base: u128 = base;
let mut remaining = n;
while remaining > 0 {
if remaining & 1 == 1 {
int_result = match checked_mul_div_u(int_result, pow_base, SCALE) {
Some(v) if v > 0 => v,
_ => return Ok(0),
};
}
remaining >>= 1;
if remaining > 0 {
pow_base = match checked_mul_div_u(pow_base, pow_base, SCALE) {
Some(v) if v > 0 => v,
_ => {
if base < SCALE {
return Ok(0);
} else {
return Err(SolMathError::Overflow);
}
}
};
}
}
let frac_result = if frac_std == 0 {
SCALE
} else {
let frac_hp = upscale_std_to_hp(frac_std)?;
let frac_product = fp_mul_hp_i(frac_hp, ln_base)?;
let frac_hp_result = exp_fixed_hp(frac_product)?;
downscale_hp_to_std(frac_hp_result)
};
match checked_mul_div_u(int_result, frac_result, SCALE) {
Some(v) => Ok(v),
None => {
if base < SCALE {
Ok(0)
} else {
Err(SolMathError::Overflow)
}
}
}
}
pub fn pow_product_hp(x: u128, w: u128) -> Result<u128, SolMathError> {
if x == 0 {
return Err(SolMathError::DomainError);
}
if w > SCALE {
return Err(SolMathError::DomainError);
}
if x == SCALE {
return Ok(SCALE);
}
if w == 0 || w == SCALE {
return Ok(x);
}
let x_hp = upscale_std_to_hp(x)?;
let w_hp = upscale_std_to_hp(w)?;
let ln_x = ln_fixed_hp(x_hp)?;
let a = fp_mul_hp_i(w_hp, ln_x)?;
let b = ln_x - a;
let exp_a = exp_fixed_hp(a)?;
let exp_b = exp_fixed_hp(b)?;
if exp_a <= 0 || exp_b <= 0 {
return Ok(0);
}
let product_hp = fp_mul_hp_i(exp_a, exp_b)?;
Ok(downscale_hp_to_std(product_hp))
}
#[inline]
pub(crate) fn horner_hp_13(c: &[i128; 14], t: i128) -> Result<i128, SolMathError> {
let mut r = c[13];
r = fp_mul_hp_i(r, t)? + c[12];
r = fp_mul_hp_i(r, t)? + c[11];
r = fp_mul_hp_i(r, t)? + c[10];
r = fp_mul_hp_i(r, t)? + c[9];
r = fp_mul_hp_i(r, t)? + c[8];
r = fp_mul_hp_i(r, t)? + c[7];
r = fp_mul_hp_i(r, t)? + c[6];
r = fp_mul_hp_i(r, t)? + c[5];
r = fp_mul_hp_i(r, t)? + c[4];
r = fp_mul_hp_i(r, t)? + c[3];
r = fp_mul_hp_i(r, t)? + c[2];
r = fp_mul_hp_i(r, t)? + c[1];
r = fp_mul_hp_i(r, t)? + c[0];
Ok(r)
}
#[inline]
pub(crate) fn horner_hp_15(c: &[i128; 16], t: i128) -> Result<i128, SolMathError> {
let mut r = c[15];
r = fp_mul_hp_i(r, t)? + c[14];
r = fp_mul_hp_i(r, t)? + c[13];
r = fp_mul_hp_i(r, t)? + c[12];
r = fp_mul_hp_i(r, t)? + c[11];
r = fp_mul_hp_i(r, t)? + c[10];
r = fp_mul_hp_i(r, t)? + c[9];
r = fp_mul_hp_i(r, t)? + c[8];
r = fp_mul_hp_i(r, t)? + c[7];
r = fp_mul_hp_i(r, t)? + c[6];
r = fp_mul_hp_i(r, t)? + c[5];
r = fp_mul_hp_i(r, t)? + c[4];
r = fp_mul_hp_i(r, t)? + c[3];
r = fp_mul_hp_i(r, t)? + c[2];
r = fp_mul_hp_i(r, t)? + c[1];
r = fp_mul_hp_i(r, t)? + c[0];
Ok(r)
}
#[inline]
pub(crate) fn horner_hp_17(c: &[i128; 18], t: i128) -> Result<i128, SolMathError> {
let mut r = c[17];
r = fp_mul_hp_i(r, t)? + c[16];
r = fp_mul_hp_i(r, t)? + c[15];
r = fp_mul_hp_i(r, t)? + c[14];
r = fp_mul_hp_i(r, t)? + c[13];
r = fp_mul_hp_i(r, t)? + c[12];
r = fp_mul_hp_i(r, t)? + c[11];
r = fp_mul_hp_i(r, t)? + c[10];
r = fp_mul_hp_i(r, t)? + c[9];
r = fp_mul_hp_i(r, t)? + c[8];
r = fp_mul_hp_i(r, t)? + c[7];
r = fp_mul_hp_i(r, t)? + c[6];
r = fp_mul_hp_i(r, t)? + c[5];
r = fp_mul_hp_i(r, t)? + c[4];
r = fp_mul_hp_i(r, t)? + c[3];
r = fp_mul_hp_i(r, t)? + c[2];
r = fp_mul_hp_i(r, t)? + c[1];
r = fp_mul_hp_i(r, t)? + c[0];
Ok(r)
}
#[inline]
pub(crate) fn mills_ratio_cf8_hp(x: i128) -> Result<i128, SolMathError> {
let mut r = 0i128;
for k in (1..=8).rev() {
r = fp_div_hp_safe((k as i128) * SCALE_HP, x + r)?;
}
fp_div_hp_safe(SCALE_HP, x + r)
}
pub fn norm_cdf_poly_hp(x: i128) -> Result<i128, SolMathError> {
if x < -8 * SCALE_HP {
return Ok(0);
}
if x > 8 * SCALE_HP {
return Ok(SCALE_HP);
}
if x == 0 {
return Ok(SCALE_HP / 2);
}
let ax = x.abs();
let cdf_pos = if ax <= POLY_HP_V2_I0_HI {
horner_hp_13(
&POLY_HP_V2_I0,
poly_map_t_hp(ax, POLY_HP_V2_I0_MID, POLY_HP_V2_I0_HW)?,
)?
} else if ax <= POLY_HP_V2_I1_HI {
horner_hp_13(
&POLY_HP_V2_I1,
poly_map_t_hp(ax, POLY_HP_V2_I1_MID, POLY_HP_V2_I1_HW)?,
)?
} else if ax <= POLY_HP_V2_I2A_HI {
horner_hp_15(
&POLY_HP_V2_I2A,
poly_map_t_hp(ax, POLY_HP_V2_I2A_MID, POLY_HP_V2_I2A_HW)?,
)?
} else if ax <= POLY_HP_V2_I2B_HI {
horner_hp_15(
&POLY_HP_V2_I2B,
poly_map_t_hp(ax, POLY_HP_V2_I2B_MID, POLY_HP_V2_I2B_HW)?,
)?
} else if ax <= POLY_HP_V2_I3A_HI {
horner_hp_17(
&POLY_HP_V2_I3A,
poly_map_t_hp(ax, POLY_HP_V2_I3A_MID, POLY_HP_V2_I3A_HW)?,
)?
} else if ax <= 5 * SCALE_HP {
horner_hp_17(
&POLY_HP_V2_I3B,
poly_map_t_hp(ax, POLY_HP_V2_I3B_MID, POLY_HP_V2_I3B_HW)?,
)?
} else {
let t = poly_map_t_hp(ax, POLY_HP_I4_MID, POLY_HP_I4_HW)?;
let pdf_hp = horner_hp_17(&POLY_HP_I4_PDF, t)?.max(0);
let tail_hp = fp_mul_hp_i(pdf_hp, mills_ratio_cf8_hp(ax)?)?;
SCALE_HP - tail_hp
};
let cdf_pos = cdf_pos.clamp(0, SCALE_HP);
Ok(if x >= 0 {
cdf_pos
} else {
SCALE_HP - cdf_pos
})
}
#[inline]
pub(crate) fn poly_map_t_hp(ax: i128, mid: i128, hw: i128) -> Result<i128, SolMathError> {
let product = (ax - mid).checked_mul(SCALE_HP).ok_or(SolMathError::Overflow)?;
Ok(product / hw)
}
pub(crate) struct BsIntermediatesHp {
pub s_hp: i128,
#[allow(dead_code)]
pub k_hp: i128,
pub d1_hp: i128,
#[allow(dead_code)]
pub d2_hp: i128,
pub phi_d1_hp: i128,
pub phi_d2_hp: i128,
pub phi_neg_d1_hp: i128,
pub phi_neg_d2_hp: i128,
pub k_disc_hp: i128,
pub sigma_sqrt_t_hp: i128,
pub sqrt_t_hp: i128,
pub sigma_hp: i128,
pub r_hp: i128,
pub t_hp: i128,
}
pub(crate) fn compute_bs_intermediates_hp(
s: u128, k: u128, r: u128, sigma: u128, t: u128,
) -> Result<BsIntermediatesHp, SolMathError> {
let s_hp = upscale_std_to_hp(s)?;
let k_hp = upscale_std_to_hp(k)?;
let r_hp = upscale_std_to_hp(r)?;
let sigma_hp = upscale_std_to_hp(sigma)?;
let t_hp = upscale_std_to_hp(t)?;
let sqrt_t_hp = isqrt_u128((t_hp as u128).checked_mul(SCALE_HP_U).ok_or(SolMathError::Overflow)?) as i128;
let sigma_sqrt_t_hp = fp_mul_hp_i(sigma_hp, sqrt_t_hp)?;
let sk_ratio_hp = fp_div_hp_safe(s_hp, k_hp)?;
let ln_sk_hp = ln_fixed_hp(sk_ratio_hp)?;
let sigma_sq_half_hp = fp_mul_hp_i(sigma_hp, sigma_hp)? / 2;
let drift_hp = fp_mul_hp_i(r_hp + sigma_sq_half_hp, t_hp)?;
let d1_num_hp = ln_sk_hp + drift_hp;
let d1_hp = if sigma_sqrt_t_hp > 0 {
fp_div_hp_safe(d1_num_hp, sigma_sqrt_t_hp)?
} else {
0
};
let d2_hp = d1_hp - sigma_sqrt_t_hp;
let phi_d1_hp = norm_cdf_poly_hp(d1_hp)?;
let phi_d2_hp = norm_cdf_poly_hp(d2_hp)?;
let phi_neg_d1_hp = SCALE_HP - phi_d1_hp;
let phi_neg_d2_hp = SCALE_HP - phi_d2_hp;
let r_t_hp = fp_mul_hp_i(r_hp, t_hp)?;
let discount_hp = exp_fixed_hp(-r_t_hp)?;
let k_disc_hp = fp_mul_hp_i(k_hp, discount_hp)?;
Ok(BsIntermediatesHp {
s_hp, k_hp, d1_hp, d2_hp,
phi_d1_hp, phi_d2_hp, phi_neg_d1_hp, phi_neg_d2_hp,
k_disc_hp, sigma_sqrt_t_hp, sqrt_t_hp,
sigma_hp, r_hp, t_hp,
})
}
pub fn black_scholes_price_hp(
s: u128, k: u128, r: u128, sigma: u128, t: u128,
) -> Result<(u128, u128), SolMathError> {
if s > i128::MAX as u128 || k > i128::MAX as u128 || r > i128::MAX as u128
|| sigma > i128::MAX as u128 || t > i128::MAX as u128
{
return Err(SolMathError::Overflow);
}
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 || k == 0 {
if s == 0 {
let r_t = fp_mul_i(r as i128, t as i128)?;
let k_disc = fp_mul_i(k as i128, exp_fixed_i(-r_t)?)?;
let put = if k_disc > 0 { k_disc as u128 } else { 0 };
return Ok((0, put));
}
return Ok((s, 0));
}
let im = compute_bs_intermediates_hp(s, k, r, sigma, t)?;
let call_hp = fp_mul_hp_i(im.s_hp, im.phi_d1_hp)? - fp_mul_hp_i(im.k_disc_hp, im.phi_d2_hp)?;
let put_hp = fp_mul_hp_i(im.k_disc_hp, im.phi_neg_d2_hp)? - fp_mul_hp_i(im.s_hp, im.phi_neg_d1_hp)?;
let call = downscale_hp_to_std(call_hp);
let put = downscale_hp_to_std(put_hp);
Ok((call, put))
}
pub fn bs_full_hp(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<BsFull, SolMathError> {
if s > i128::MAX as u128 || k > i128::MAX as u128 || r > i128::MAX as u128
|| sigma > i128::MAX as u128 || t > i128::MAX as u128
{
return Err(SolMathError::Overflow);
}
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 || k == 0 {
let zero_full = BsFull {
call: if s > 0 { s } else { 0 },
put: if s == 0 {
let r_t = fp_mul_i(r as i128, t as i128)?;
let kd = fp_mul_i(k as i128, exp_fixed_i(-r_t)?)?;
if kd > 0 { kd as u128 } else { 0 }
} else { 0 },
call_delta: if s == 0 { 0 } else { SCALE_I },
put_delta: if s == 0 { -SCALE_I } else { 0 },
gamma: 0, vega: 0, call_theta: 0, put_theta: 0, call_rho: 0, put_rho: 0,
};
return Ok(zero_full);
}
let im = compute_bs_intermediates_hp(s, k, r, sigma, t)?;
let d1_sq_half_hp = fp_mul_hp_i(im.d1_hp, im.d1_hp)? / 2;
let exp_neg_hp = exp_fixed_hp(-d1_sq_half_hp)?;
let pdf_d1_hp = fp_mul_hp_i(exp_neg_hp, INV_SQRT_2PI_HP)?;
let call_hp = fp_mul_hp_i(im.s_hp, im.phi_d1_hp)? - fp_mul_hp_i(im.k_disc_hp, im.phi_d2_hp)?;
let put_hp = fp_mul_hp_i(im.k_disc_hp, im.phi_neg_d2_hp)? - fp_mul_hp_i(im.s_hp, im.phi_neg_d1_hp)?;
let call = downscale_hp_to_std(call_hp);
let put = downscale_hp_to_std(put_hp);
let phi_d1_std = downscale_hp_to_std_i(im.phi_d1_hp);
let call_delta = phi_d1_std;
let put_delta = phi_d1_std - SCALE_I;
let gamma_denom_hp = fp_mul_hp_i(im.s_hp, im.sigma_sqrt_t_hp)?;
let gamma_hp = if gamma_denom_hp != 0 {
fp_div_hp_safe(pdf_d1_hp, gamma_denom_hp)?
} else {
0
};
let gamma = downscale_hp_to_std_i(gamma_hp);
let vega_hp = fp_mul_hp_i(fp_mul_hp_i(im.s_hp, pdf_d1_hp)?, im.sqrt_t_hp)?;
let vega = downscale_hp_to_std_i(vega_hp);
let spd_sigma_hp = fp_mul_hp_i(fp_mul_hp_i(im.s_hp, pdf_d1_hp)?, im.sigma_hp)?;
let two_sqrt_t_hp = 2 * im.sqrt_t_hp;
let theta_common_hp = if two_sqrt_t_hp > 0 {
-fp_div_hp_safe(spd_sigma_hp, two_sqrt_t_hp)?
} else {
0
};
let r_k_disc_hp = fp_mul_hp_i(im.r_hp, im.k_disc_hp)?;
let call_theta_hp = theta_common_hp - fp_mul_hp_i(r_k_disc_hp, im.phi_d2_hp)?;
let put_theta_hp = theta_common_hp + fp_mul_hp_i(r_k_disc_hp, im.phi_neg_d2_hp)?;
let call_theta = downscale_hp_to_std_i(call_theta_hp);
let put_theta = downscale_hp_to_std_i(put_theta_hp);
let kt_disc_hp = fp_mul_hp_i(im.k_disc_hp, im.t_hp)?;
let call_rho_hp = fp_mul_hp_i(kt_disc_hp, im.phi_d2_hp)?;
let put_rho_hp = -fp_mul_hp_i(kt_disc_hp, im.phi_neg_d2_hp)?;
let call_rho = downscale_hp_to_std_i(call_rho_hp);
let put_rho = downscale_hp_to_std_i(put_rho_hp);
Ok(BsFull {
call,
put,
call_delta,
put_delta,
gamma,
vega,
call_theta,
put_theta,
call_rho,
put_rho,
})
}
#[inline]
pub(crate) fn fp_mul_hp_fast_dw(a: i128, b: i128) -> DoubleWord {
let p = a * b;
let q = if p >= 0 {
(p + SCALE_HP / 2) / SCALE_HP
} else {
(p - SCALE_HP / 2) / SCALE_HP
};
let r = p - q * SCALE_HP;
DoubleWord::new_raw(q, r)
}
#[allow(dead_code)]
pub(crate) fn horner_compensated_hp(coeffs: &[i128], t: i128) -> Result<i128, SolMathError> {
Ok(horner_compensated_hp_dw(coeffs, t)?.to_i128_at_scale(SCALE_HP))
}
pub(crate) fn horner_compensated_hp_dw(coeffs: &[i128], t: i128) -> Result<DoubleWord, SolMathError> {
let n = coeffs.len();
if n <= 1 {
return Ok(DoubleWord::from_hi(if n == 1 { coeffs[0] } else { 0 }));
}
let mut s = coeffs[n - 1];
let mut comp: i128 = 0;
for i in (0..n - 1).rev() {
let dw = fp_mul_hp_fast_dw(s, t);
s = dw.hi() + coeffs[i];
let comp_propagated = comp.checked_mul(t)
.ok_or(SolMathError::Overflow)? / SCALE_HP;
comp = comp_propagated + dw.lo();
}
Ok(DoubleWord::new_raw(s, comp))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::{SCALE_HP, LN_REMEZ_HP_COEFFS};
#[test]
fn test_hp_dw_zero() {
let dw = fp_mul_hp_fast_dw(0, SCALE_HP);
assert_eq!(dw.hi(), 0);
assert_eq!(dw.lo(), 0);
let dw2 = fp_mul_hp_fast_dw(SCALE_HP, 0);
assert_eq!(dw2.hi(), 0);
assert_eq!(dw2.lo(), 0);
}
#[test]
fn test_hp_dw_exact_one() {
let dw = fp_mul_hp_fast_dw(SCALE_HP, SCALE_HP);
assert_eq!(dw.hi(), SCALE_HP);
assert_eq!(dw.lo(), 0);
}
#[test]
fn test_hp_dw_exact_product() {
let dw = fp_mul_hp_fast_dw(2 * SCALE_HP, 3 * SCALE_HP);
assert_eq!(dw.hi(), 6 * SCALE_HP);
assert_eq!(dw.lo(), 0);
}
#[test]
fn test_hp_dw_consistency_with_fp_mul_hp_fast() {
let values: &[i128] = &[
1, -1, 2, -2,
SCALE_HP / 2, -SCALE_HP / 2,
SCALE_HP, -SCALE_HP,
SCALE_HP + 1, -(SCALE_HP + 1),
SCALE_HP * 2, -(SCALE_HP * 2),
SCALE_HP / 3, -(SCALE_HP / 3),
SCALE_HP / 7, -(SCALE_HP / 7),
SCALE_HP * 50, -(SCALE_HP * 50),
999_999_999_999_999, -999_999_999_999_999,
500_000_000_000_001, -500_000_000_000_001,
];
for &a in values {
for &b in values {
if a.checked_mul(b).is_some() {
let expected = fp_mul_hp_fast(a, b);
let dw = fp_mul_hp_fast_dw(a, b);
assert_eq!(dw.hi(), expected,
"hi mismatch: a={}, b={}, expected={}, got={}", a, b, expected, dw.hi());
}
}
}
}
#[test]
fn test_hp_dw_residual_bounded() {
let values: &[i128] = &[
0, 1, -1, SCALE_HP, -SCALE_HP, SCALE_HP / 3, -SCALE_HP / 7,
SCALE_HP * 50, -SCALE_HP * 50, 999_999_999_999_999,
];
for &a in values {
for &b in values {
let dw = fp_mul_hp_fast_dw(a, b);
assert!(dw.lo().abs() < SCALE_HP,
"lo out of bounds: a={}, b={}, lo={}", a, b, dw.lo());
}
}
}
#[test]
fn test_hp_dw_negative() {
let dw = fp_mul_hp_fast_dw(-SCALE_HP, SCALE_HP);
assert_eq!(dw.hi(), -SCALE_HP);
assert_eq!(dw.lo(), 0);
}
#[test]
fn test_hp_dw_identity() {
for a in [1i128, -1, SCALE_HP / 3, -SCALE_HP * 7, 42 * SCALE_HP] {
let dw = fp_mul_hp_fast_dw(a, SCALE_HP);
assert_eq!(dw.hi(), a, "Identity failed for a={}", a);
assert_eq!(dw.lo(), 0, "Identity residual nonzero for a={}", a);
}
}
#[test]
fn test_hp_dw_small_inputs() {
let dw = fp_mul_hp_fast_dw(1, 1);
assert_eq!(dw.hi(), 0);
assert_eq!(dw.lo(), 1);
}
#[test]
fn test_horner_hp_constant() {
let coeffs = [42 * SCALE_HP];
assert_eq!(horner_compensated_hp(&coeffs, SCALE_HP).unwrap(), 42 * SCALE_HP);
assert_eq!(horner_compensated_hp(&coeffs, 0).unwrap(), 42 * SCALE_HP);
assert_eq!(horner_compensated_hp(&coeffs, -SCALE_HP).unwrap(), 42 * SCALE_HP);
}
#[test]
fn test_horner_hp_linear() {
let coeffs = [5 * SCALE_HP, 3 * SCALE_HP];
assert_eq!(horner_compensated_hp(&coeffs, 2 * SCALE_HP).unwrap(), 11 * SCALE_HP);
}
#[test]
fn test_horner_hp_at_zero() {
let coeffs = [7 * SCALE_HP, 3 * SCALE_HP, SCALE_HP];
assert_eq!(horner_compensated_hp(&coeffs, 0).unwrap(), 7 * SCALE_HP);
}
#[test]
fn test_horner_hp_exact_quadratic() {
let coeffs = [SCALE_HP, 2 * SCALE_HP, 3 * SCALE_HP];
assert_eq!(horner_compensated_hp(&coeffs, SCALE_HP).unwrap(), 6 * SCALE_HP);
}
#[test]
fn test_horner_hp_consistency_with_inline() {
let coeffs = &LN_REMEZ_HP_COEFFS;
let test_u: &[i128] = &[0, SCALE_HP / 20, SCALE_HP / 9];
for &u in test_u {
let mut r = coeffs[9];
for i in (0..9).rev() {
r = fp_mul_hp_fast(r, u) + coeffs[i];
}
let compensated = horner_compensated_hp(&coeffs[..], u).unwrap();
let diff = (compensated - r).abs();
assert!(diff <= 1,
"u={}: inline={}, compensated={}, diff={}", u, r, compensated, diff);
}
}
#[test]
fn test_horner_hp_dw_residual_bounded() {
let coeffs = &LN_REMEZ_HP_COEFFS;
for u in [0i128, SCALE_HP / 20, SCALE_HP / 9, -SCALE_HP / 9] {
let dw = horner_compensated_hp_dw(&coeffs[..], u).unwrap();
assert!(dw.lo().abs() < 12 * SCALE_HP,
"u={}: lo={} exceeds 12*SCALE_HP", u, dw.lo());
}
}
#[test]
fn test_fp_mul_hp_i_allows_exact_i128_min() {
let got = fp_mul_hp_i(i128::MIN, SCALE_HP).unwrap();
assert_eq!(got, i128::MIN);
}
#[test]
fn test_fp_mul_hp_i_rejects_magnitude_above_i128_min() {
assert_eq!(fp_mul_hp_i(i128::MIN, SCALE_HP + 1), Err(SolMathError::Overflow));
}
#[test]
fn test_horner_hp_no_panic() {
let coeffs = &LN_REMEZ_HP_COEFFS;
for u in [-SCALE_HP / 9, -SCALE_HP / 20, 0, SCALE_HP / 20, SCALE_HP / 9] {
let _ = horner_compensated_hp(&coeffs[..], u).unwrap();
let _ = horner_compensated_hp_dw(&coeffs[..], u).unwrap();
}
}
}