use super::ball::Cache;
use super::wp::{RM, Wp};
use crate::base::bigcomplex::{Complex, c_add, c_div, c_mul, c_sub};
use crate::base::bigfloat::is_strictly_positive;
use crate::base::libfn::LibFn;
use crate::base::node::ExprId;
use astro_float::BigFloat;
use crate::base::errors::SymplexError;
fn unevaluable(reason: impl Into<String>) -> SymplexError {
SymplexError::Unevaluable {
reason: reason.into(),
}
}
fn pos(x: &BigFloat) -> bool {
is_strictly_positive(x)
}
fn is_npint(x: &BigFloat) -> bool {
x.is_zero() || (x.is_int() && x.is_negative())
}
fn is_even(x: &BigFloat) -> bool {
if x.is_zero() {
return true;
}
let mut half = x.clone();
match x.exponent() {
Some(e) => {
half.set_exponent(e - 1);
half.is_int()
}
None => true,
}
}
fn gamma_sign(x: &BigFloat) -> i32 {
if pos(x) || is_even(&x.floor()) { 1 } else { -1 }
}
pub(super) fn exact_sum(a: &BigFloat, b: &BigFloat, prec: usize) -> BigFloat {
let span = |x: &BigFloat| -> Option<(i64, i64)> {
let e = i64::from(x.exponent()?);
let m = x.mantissa_max_bit_len().unwrap_or(64) as i64;
(!x.is_zero()).then_some((e, e - m))
};
let p = match (span(a), span(b)) {
(Some((ea, la)), Some((eb, lb))) => {
let bits = ea.max(eb) + 2 - la.min(lb);
usize::try_from(bits).unwrap_or(prec).min(prec + (1 << 18))
}
(Some((ea, la)), None) | (None, Some((ea, la))) => {
usize::try_from(ea - la + 2).unwrap_or(prec)
}
(None, None) => prec,
}
.max(64);
a.add(b, p, RM)
}
fn int(n: i32) -> BigFloat {
BigFloat::from_i32(n, 64)
}
pub(super) fn gamma_ratio(
num: &[BigFloat],
den: &[BigFloat],
w: &mut Wp<'_>,
) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if num.iter().chain(den).any(|x| x.is_nan() || x.is_inf()) {
return Err(unevaluable("Gamma quotient of a non-finite argument"));
}
let (poles_num, mut regular_num): (Vec<&BigFloat>, Vec<BigFloat>) = split_poles(num);
let (poles_den, mut regular_den): (Vec<&BigFloat>, Vec<BigFloat>) = split_poles(den);
if poles_num.len() < poles_den.len() {
return Ok(BigFloat::new(prec));
}
if poles_num.len() > poles_den.len() {
return Err(unevaluable(
"the Gamma quotient has a pole here (more poles of Gamma above than below): the value is infinite",
));
}
let mut sign = 1;
for (i, j) in poles_num.into_iter().zip(poles_den) {
if !is_even(&exact_sum(i, j, prec)) {
sign = -sign;
}
regular_num.push(exact_sum(&int(1), &j.neg(), prec));
regular_den.push(exact_sum(&int(1), &i.neg(), prec));
}
let unit = |x: &BigFloat| x.cmp(&int(1)) == Some(0) || x.cmp(&int(2)) == Some(0);
regular_num.retain(|x| !unit(x));
regular_den.retain(|x| !unit(x));
for x in regular_num.iter().chain(®ular_den) {
sign *= gamma_sign(x);
}
let mut wp = prec + 64;
loop {
let mut total = BigFloat::new(wp);
let mut size = 0.0f64;
for (x, plus) in regular_num
.iter()
.map(|x| (x, true))
.chain(regular_den.iter().map(|x| (x, false)))
{
let l = super::arb_log_gamma(x, &mut w.at(wp))?.0;
size += super::f64_of(&l).unwrap_or(f64::INFINITY).abs();
total = if plus {
total.add(&l, wp, RM)
} else {
total.sub(&l, wp, RM)
};
}
let needed = prec + 32 + (size + 1.0).log2().ceil().max(0.0) as usize;
if needed <= wp || !size.is_finite() {
let v = total.exp(wp, RM, w.cc);
if v.is_inf() || v.is_nan() || (v.is_zero() && !total.is_zero()) {
return Err(unevaluable(
"the Gamma quotient is beyond the floating-point exponent range",
));
}
let v = super::round_to(v, prec);
return Ok(if sign < 0 { v.neg() } else { v });
}
wp = needed;
}
}
fn split_poles(xs: &[BigFloat]) -> (Vec<&BigFloat>, Vec<BigFloat>) {
let mut poles = Vec::new();
let mut regular = Vec::new();
for x in xs {
if is_npint(x) {
poles.push(x);
} else {
regular.push(x.clone());
}
}
(poles, regular)
}
pub(super) fn rising_factorial(
x: &BigFloat,
n: &BigFloat,
w: &mut Wp<'_>,
) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if n.is_zero() {
return Ok(BigFloat::from_i32(1, prec));
}
let xn = exact_sum(x, n, prec);
gamma_ratio(&[xn], std::slice::from_ref(x), w)
}
pub(super) fn falling_factorial(
x: &BigFloat,
n: &BigFloat,
w: &mut Wp<'_>,
) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if n.is_zero() {
return Ok(BigFloat::from_i32(1, prec));
}
let x1 = exact_sum(x, &int(1), prec);
let xn1 = exact_sum(&x1, &n.neg(), prec);
gamma_ratio(&[x1], &[xn1], w)
}
pub(super) fn nonreal_args(f: LibFn, args: &[ExprId], cache: Cache<'_>) -> bool {
if !matches!(f, LibFn::RisingFactorial | LibFn::FallingFactorial) {
return false;
}
let vals: Option<Vec<&Complex>> = args.iter().map(|a| cache.get(a)).collect();
vals.is_some_and(|vals| {
vals.iter().any(|v| !v.1.is_zero())
&& vals
.iter()
.all(|v| [&v.0, &v.1].iter().all(|x| !x.is_nan() && !x.is_inf()))
})
}
pub(super) fn eval_complex(
f: LibFn,
args: &[ExprId],
cache: Cache<'_>,
w: &mut Wp<'_>,
) -> Result<Complex, SymplexError> {
let prec = w.bits();
let (Some(x), Some(n)) = (
args.first().and_then(|a| cache.get(a)),
args.get(1).and_then(|a| cache.get(a)),
) else {
return Err(unevaluable("rising/falling factorial lost its arguments"));
};
let rising = f == LibFn::RisingFactorial;
let cap = super::sensitivity::gap_cap(prec);
let mut extra = 32usize;
for _ in 0..4 {
let wp = prec + extra;
let (value, lost) = complex_quotient(rising, x, n, &mut w.at(wp))?;
let gap = part_gap(&value).min(cap);
if lost + gap + 16 <= extra {
return Ok((
super::round_to(value.0, prec),
super::round_to(value.1, prec),
));
}
if lost > 4 * prec + 512 {
break;
}
extra = lost + gap + 32;
}
Err(unevaluable(format!(
"{}: the value cancels beyond the working precision",
f.name()
)))
}
pub(super) fn exact_product(f: LibFn, args: &[ExprId], cache: Cache<'_>, prec: usize) -> bool {
let (Some(x), Some(n)) = (
args.first().and_then(|a| cache.get(a)),
args.get(1).and_then(|a| cache.get(a)),
) else {
return false;
};
if !args
.iter()
.all(|a| cache.bound(a).is_some_and(|b| b.is_exact()))
{
return false;
}
let Some(k) = integer_steps(n) else {
return false;
};
if k == 0 {
return true;
}
if k < 0 || [&x.0, &x.1].iter().any(|p| p.inexact()) {
return false;
}
let rising = f == LibFn::RisingFactorial;
let mut acc = (BigFloat::from_i32(1, prec), BigFloat::new(prec));
for j in 0..k.unsigned_abs() {
let offset = BigFloat::from_u64(j, prec);
let re = if rising {
x.0.add(&offset, prec, RM)
} else {
x.0.sub(&offset, prec, RM)
};
acc = c_mul(&acc, &(re, x.1.clone()), prec, RM);
if [&acc.0, &acc.1].iter().any(|p| p.inexact()) {
return false;
}
}
true
}
fn integer_steps(n: &Complex) -> Option<i64> {
(n.1.is_zero() && (n.0.is_zero() || n.0.is_int()))
.then(|| crate::base::bigfloat::to_f64(&n.0).and_then(crate::base::cast::f64_to_i64))
.flatten()
.filter(|k| k.unsigned_abs() <= 4096)
}
fn part_gap(v: &Complex) -> usize {
let e = |x: &BigFloat| {
(!x.is_zero())
.then(|| x.exponent().map(i64::from))
.flatten()
};
match (e(&v.0), e(&v.1)) {
(Some(a), Some(b)) => usize::try_from((a - b).abs()).unwrap_or(usize::MAX / 8),
_ => 0,
}
}
fn complex_quotient(
rising: bool,
x: &Complex,
n: &Complex,
w: &mut Wp<'_>,
) -> Result<(Complex, usize), SymplexError> {
let wp = w.bits();
let one = (BigFloat::from_i32(1, wp), BigFloat::new(wp));
if let Some(k) = integer_steps(n) {
let up = rising == (k > 0);
let mut acc = one.clone();
for j in 0..k.unsigned_abs() {
let off = if k > 0 { j } else { j + 1 };
let offset = BigFloat::from_u64(off, wp);
let factor = if up {
(x.0.add(&offset, wp, RM), x.1.clone())
} else {
(x.0.sub(&offset, wp, RM), x.1.clone())
};
acc = c_mul(&acc, &factor, wp, RM);
}
let lost = usize::try_from(super::accuracy::ceil_log2(k.unsigned_abs() as usize + 1))
.unwrap_or(0)
+ 3;
if k >= 0 {
return Ok((acc, lost));
}
if acc.0.is_zero() && acc.1.is_zero() {
return Err(unevaluable("the value is infinite (a pole of Gamma)"));
}
return Ok((c_div(&one, &acc, wp, RM), lost));
}
let (u, d) = if rising {
(c_add(x, n, wp, RM), x.clone())
} else {
let x1 = c_add(x, &one, wp, RM);
(x1.clone(), c_sub(&x1, n, wp, RM))
};
let pole = |z: &Complex| z.1.is_zero() && is_npint(&z.0);
if pole(&d) && !pole(&u) {
return Ok(((BigFloat::new(wp), BigFloat::new(wp)), 0));
}
if pole(&u) {
return Err(unevaluable("the value is infinite (a pole of Gamma)"));
}
let log_gamma = |z: &Complex, w: &mut Wp<'_>| -> Result<Complex, SymplexError> {
if z.1.is_zero() && z.0.exponent().is_none_or(|e| e > 10) {
super::arb_log_gamma(&z.0, w)
} else if z.1.is_zero() {
let p = w.bits();
let g = (super::arb_gamma_real(&z.0, w)?, BigFloat::new(p));
Ok(super::c_ln(&g, w))
} else {
super::loggamma::loggamma_complex(z, w)
}
};
let rough = |z: &Complex, w: &mut Wp<'_>| -> Result<i64, SymplexError> {
Ok(super::accuracy::mag(&log_gamma(z, &mut w.at(64))?)
.unwrap_or(0)
.max(0))
};
let big = rough(&u, w)?.max(rough(&d, w)?) + 2;
let p = wp + usize::try_from(big).unwrap_or(0);
let lu = log_gamma(&u, &mut w.at(p))?;
let ld = log_gamma(&d, &mut w.at(p))?;
let diff = c_sub(&lu, &ld, p, RM);
let limit = f64::from(astro_float::EXPONENT_MAX) * std::f64::consts::LN_2;
if crate::base::bigfloat::to_f64(&diff.0).is_none_or(|r| r.abs() > limit) {
return Err(unevaluable("Gamma quotient beyond the exponent range"));
}
Ok((super::c_exp(&diff, &mut w.at(wp)), 4))
}
pub(super) fn binomial(
n: &BigFloat,
k: &BigFloat,
w: &mut Wp<'_>,
) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if k.is_int() && k.is_negative() && !k.is_zero() {
return Ok(BigFloat::new(prec));
}
let n1 = exact_sum(n, &int(1), prec);
let k1 = exact_sum(k, &int(1), prec);
let nk1 = exact_sum(&n1, &k.neg(), prec);
gamma_ratio(&[n1], &[k1, nk1], w)
}
pub(super) fn binomial_meets_pole(n: &BigFloat, k: &BigFloat, prec: usize) -> bool {
let n1 = exact_sum(n, &int(1), prec);
let k1 = exact_sum(k, &int(1), prec);
let nk1 = exact_sum(&n1, &k.neg(), prec);
is_npint(&n1) || is_npint(&k1) || is_npint(&nk1)
}
pub(super) fn catalan(x: &BigFloat, w: &mut Wp<'_>) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if x.is_nan() || x.is_inf() {
return Err(unevaluable("catalan of a non-finite argument"));
}
if is_npint(x) && !x.is_zero() {
return Ok(if x.cmp(&int(-1)) == Some(0) {
int(-1).div(&int(2), prec, RM)
} else {
BigFloat::new(prec)
});
}
let numerator = exact_sum(&exact_sum(x, x, prec), &int(1), prec);
if is_npint(&numerator) {
return Err(unevaluable(
"catalan has a pole at a negative half-integer (the value is infinite)",
));
}
let x1 = exact_sum(x, &int(1), prec);
let x2 = exact_sum(x, &int(2), prec);
gamma_ratio(&[numerator], &[x1, x2], w)
}
fn cos_pi(x: &BigFloat, w: &mut Wp<'_>) -> BigFloat {
let wp = w.bits();
let half = BigFloat::from_f64(0.5, 64);
let mut k = x.floor();
let mut f = exact_sum(x, &k.neg(), wp);
if f.cmp(&half).is_some_and(|c| c > 0) {
f = exact_sum(&f, &int(-1), wp);
k = exact_sum(&k, &int(1), wp);
}
let g = exact_sum(&half, &f.abs().neg(), wp);
let s = if g.is_zero() {
BigFloat::new(wp)
} else {
w.cc.pi(wp, RM).clone().mul(&g, wp, RM).sin(wp, RM, w.cc)
};
if is_even(&k) { s } else { s.neg() }
}
pub(super) fn fibonacci(
x: &BigFloat,
lucas: bool,
w: &mut Wp<'_>,
) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if x.is_nan() || x.is_inf() {
return Err(unevaluable("fibonacci of a non-finite argument"));
}
let int_bits = x
.exponent()
.map_or(0, |e| usize::try_from(e).unwrap_or(0))
.min(1 << 20);
let cap = super::cancellation_cap(prec);
let mut extra = 32 + int_bits;
loop {
let wp = prec + extra;
let sqrt5 = int(5).sqrt(wp, RM);
let phi = int(1).add(&sqrt5, wp, RM).div(&int(2), wp, RM);
let t = x.mul(&phi.ln(wp, RM, w.cc), wp, RM);
let up = t.exp(wp, RM, w.cc);
let down = t.neg().exp(wp, RM, w.cc);
if up.is_inf() || up.is_nan() || down.is_inf() || down.is_nan() {
return Err(unevaluable(
"fibonacci of this argument is beyond the floating-point exponent range",
));
}
let cosine = cos_pi(x, &mut w.at(wp));
let tail = cosine.mul(&down, wp, RM);
let sum = if lucas {
up.add(&tail, wp, RM)
} else {
up.sub(&tail, wp, RM)
};
let top = [&up, &tail]
.into_iter()
.filter(|v| !v.is_zero())
.filter_map(|v| v.exponent())
.max()
.unwrap_or(0);
let lost = match sum.exponent() {
Some(e) if !sum.is_zero() => {
usize::try_from(i64::from(top) - i64::from(e)).unwrap_or(0)
}
_ => wp,
};
if lost + 16 <= extra {
let value = if lucas { sum } else { sum.div(&sqrt5, wp, RM) };
return Ok(super::round_to(value, prec));
}
if extra >= cap {
return Err(super::special_exhausted(prec));
}
extra = (lost + 32).max(2 * extra).min(cap);
}
}
pub(super) fn factorial2(x: &BigFloat, w: &mut Wp<'_>) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if x.is_zero() || x.is_int() || x.is_nan() || x.is_inf() {
return Err(unevaluable(
"evalf has no numerical routine for 'factorial2' at an integer (eval folds it exactly)",
));
}
let wp = prec
+ 32
+ x.exponent()
.map_or(0, |e| usize::try_from(e.max(0)).unwrap_or(0));
let two = BigFloat::from_i32(2, wp);
let half_x = x.div(&two, wp, RM);
let pi = w.cc.pi(wp, RM).clone();
let c = x.mul(&pi, wp, RM).cos(wp, RM, w.cc);
let e = c
.sub(&BigFloat::from_i32(1, wp), wp, RM)
.div(&BigFloat::from_i32(4, wp), wp, RM);
let ln_half_pi = pi.div(&two, wp, RM).ln(wp, RM, w.cc);
let ln_two = two.ln(wp, RM, w.cc);
let log = half_x
.mul(&ln_two, wp, RM)
.add(&e.mul(&ln_half_pi, wp, RM), wp, RM);
let g = super::arb_gamma_real(
&half_x.add(&BigFloat::from_i32(1, wp), wp, RM),
&mut w.at(wp),
)?;
let r = log.exp(wp, RM, w.cc).mul(&g, wp, RM);
if r.is_inf() || r.is_nan() {
return Err(unevaluable(
"factorial2: the value leaves the exponent range",
));
}
Ok(super::round_to(r, prec))
}
pub(super) fn harmonic(z: &BigFloat, w: &mut Wp<'_>) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if z.is_nan() || z.is_inf() {
return Err(unevaluable("harmonic of a non-finite argument"));
}
if z.is_zero() {
return Ok(BigFloat::new(prec));
}
if z.is_int() && z.is_negative() {
return Err(unevaluable(
"harmonic has a pole at a negative integer (the value is infinite)",
));
}
let z1 = exact_sum(z, &int(1), prec);
let cap = super::cancellation_cap(prec);
let mut extra = 32;
loop {
let wp = prec + extra;
let psi = super::arb_digamma(&z1, &mut w.at(wp))?;
let gamma = super::arb_euler_gamma(&mut w.at(wp))?;
let h = psi.add(&gamma, wp, RM);
let top = psi
.exponent()
.unwrap_or(0)
.max(gamma.exponent().unwrap_or(0));
let lost = match h.exponent() {
Some(e) if !h.is_zero() => usize::try_from(i64::from(top) - i64::from(e)).unwrap_or(0),
_ => wp,
};
if lost + 16 <= extra {
return Ok(super::round_to(h, prec));
}
if extra >= cap {
return Err(super::special_exhausted(prec));
}
extra = (lost + 32).max(2 * extra).min(cap);
}
}
const MAX_ORDER: f64 = 9.0e15;
pub(super) fn polygamma_large(
n: &BigFloat,
x: &BigFloat,
w: &mut Wp<'_>,
) -> Result<BigFloat, SymplexError> {
let prec = w.bits();
if x.is_nan() || x.is_inf() {
return Err(unevaluable("polygamma of a non-finite argument"));
}
if is_npint(x) {
return Err(unevaluable("polygamma at non-positive integer pole"));
}
let s = exact_sum(n, &int(1), prec);
let sf = super::f64_of(&s)?;
if !(2.0..=MAX_ORDER).contains(&sf) {
return Err(SymplexError::NotImplemented(format!(
"polygamma of order {} (at most {MAX_ORDER:e})",
sf - 1.0
)));
}
let odd = !is_even(&s);
let cap = super::cancellation_cap(prec);
let mut extra = 32;
loop {
let wp = prec + extra;
let (scale, z) = scaled_hurwitz(&s, sf, odd, x, &mut w.at(wp))?;
let lost = match z.exponent() {
Some(e) if !z.is_zero() => usize::try_from(-i64::from(e)).unwrap_or(0),
_ => wp,
};
if lost + 16 > extra {
if extra >= cap {
return Err(super::special_exhausted(prec));
}
extra = (lost + 32).max(2 * extra).min(cap);
continue;
}
let lg = super::arb_log_gamma(&s, &mut w.at(wp))?.0;
let ln_d = scale.ln(wp, RM, w.cc);
let size = super::f64_of(&lg)?.abs() + sf * super::f64_of(&ln_d)?.abs();
let lp = prec + 32 + (size + 1.0).log2().ceil().max(0.0) as usize;
let lg = super::arb_log_gamma(&s, &mut w.at(lp))?.0;
let s_ln_d = s.mul(&scale.ln(lp, RM, w.cc), lp, RM);
let ln_z = z.abs().ln(lp, RM, w.cc);
let total = lg.sub(&s_ln_d, lp, RM).add(&ln_z, lp, RM);
let v = total.exp(lp, RM, w.cc);
if v.is_inf() || v.is_nan() || v.is_zero() {
return Err(unevaluable(
"polygamma of this order is beyond the floating-point exponent range",
));
}
let negative = odd != z.is_negative();
let v = super::round_to(v, prec);
return Ok(if negative { v.neg() } else { v });
}
}
fn scaled_hurwitz(
s: &BigFloat,
sf: f64,
odd: bool,
x: &BigFloat,
w: &mut Wp<'_>,
) -> Result<(BigFloat, BigFloat), SymplexError> {
let wp = w.bits();
if pos(x) {
return Ok((x.clone(), positive_part(s, sf, x, x, w)?));
}
let m = x.floor().neg();
let f = exact_sum(x, &m, wp);
let one_minus_f = exact_sum(&int(1), &f.neg(), wp);
let mf = super::f64_of(&m)?;
if odd && f == one_minus_f {
let start = exact_sum(&f, &m, wp);
return Ok((start.clone(), positive_part(s, sf, &start, &start, w)?));
}
let g = if f < one_minus_f {
f.clone()
} else {
one_minus_f.clone()
};
let negligible = |t: &BigFloat, sum: &BigFloat| -> bool {
match (t.exponent(), sum.exponent()) {
(_, _) if t.is_zero() => true,
(Some(te), Some(se)) if !sum.is_zero() => {
i64::from(se) - i64::from(te) > wp as i64 + 32
}
_ => false,
}
};
let mut z = BigFloat::new(wp);
let mut j = 0.0f64;
let mut u = f.clone(); let mut v = one_minus_f.clone(); while j < mf {
let a = scaled_term(s, &u, &g, w);
let b = scaled_term(s, &v, &g, w);
let pair = if odd {
a.sub(&b, wp, RM)
} else {
a.add(&b, wp, RM)
};
z = z.add(&pair, wp, RM);
if negligible(&a, &z) && negligible(&b, &z) {
return Ok((g, z));
}
j += 1.0;
u = exact_sum(&u, &int(1), wp);
v = exact_sum(&v, &int(1), wp);
if j > 1.0e6 {
return Err(SymplexError::NotImplemented(
"polygamma of large order far left of 0 (more than a million terms)".into(),
));
}
}
let rest = positive_part(s, sf, &u, &g, w)?;
Ok((g, z.add(&rest, wp, RM)))
}
fn scaled_term(s: &BigFloat, u: &BigFloat, g: &BigFloat, w: &mut Wp<'_>) -> BigFloat {
let wp = w.bits();
let s_bits = s.exponent().map_or(0, |e| usize::try_from(e).unwrap_or(0));
let p = wp + s_bits + 2 * (usize::BITS - wp.leading_zeros()) as usize + 16;
let l = u.div(g, p, RM).ln(p, RM, w.cc);
s.mul(&l, p, RM).neg().exp(wp, RM, w.cc)
}
fn positive_part(
s: &BigFloat,
sf: f64,
f: &BigFloat,
g: &BigFloat,
w: &mut Wp<'_>,
) -> Result<BigFloat, SymplexError> {
let wp = w.bits();
let switch = (sf / std::f64::consts::PI).max(wp as f64) + 1.0;
let mut sum = BigFloat::new(wp);
let mut u = f.clone();
for _ in 0..1_000_000 {
let uf = super::f64_of(&u)?;
if uf >= switch {
let tail = euler_maclaurin_tail(s, &u, g, w);
return Ok(sum.add(&tail, wp, RM));
}
let t = scaled_term(s, &u, g, w);
sum = sum.add(&t, wp, RM);
let u1 = exact_sum(&u, &int(1), wp);
let next = scaled_term(s, &u1, g, w);
let factor = 1.0 + (uf + 1.0) / (sf - 1.0);
let bound_exp =
next.exponent().map_or(i64::MIN / 2, i64::from) + factor.log2().ceil() as i64 + 1;
let sum_exp = sum.exponent().map_or(i64::MIN / 2, i64::from);
if next.is_zero() || sum_exp - bound_exp > wp as i64 + 8 {
return Ok(sum);
}
u = u1;
}
Err(SymplexError::NotImplemented(
"polygamma of large order: the Hurwitz zeta sum did not settle in a million terms".into(),
))
}
fn euler_maclaurin_tail(s: &BigFloat, a: &BigFloat, g: &BigFloat, w: &mut Wp<'_>) -> BigFloat {
let wp = w.bits();
let one = BigFloat::from_i32(1, wp);
let s_minus_1 = s.sub(&one, wp, RM);
let mut bracket = a
.div(&s_minus_1, wp, RM)
.add(&BigFloat::from_f64(0.5, wp), wp, RM);
let a2 = a.mul(a, wp, RM);
let mut coef = s.div(&a.mul(&BigFloat::from_i32(2, wp), wp, RM), wp, RM);
let mut prev: Option<i64> = None;
for j in 1usize..=(wp / 2 + 64) {
if j > 1 {
let jj = j as i64;
let p1 = s.add(&BigFloat::from_i64(2 * jj - 3, 64), wp, RM);
let p2 = s.add(&BigFloat::from_i64(2 * jj - 2, 64), wp, RM);
let q = BigFloat::from_i64((2 * jj - 1) * (2 * jj), 64);
coef = coef
.mul(&p1, wp, RM)
.mul(&p2, wp, RM)
.div(&q, wp, RM)
.div(&a2, wp, RM);
}
let b = super::ratio_to_bigfloat(&super::bernoulli::even(j), wp);
let term = b.mul(&coef, wp, RM);
let te = term.exponent().map_or(i64::MIN / 2, i64::from);
if prev.is_some_and(|p| te > p) {
break; }
prev = Some(te);
bracket = bracket.add(&term, wp, RM);
let be = bracket.exponent().map_or(0, i64::from);
if term.is_zero() || be - te > wp as i64 + 8 {
break;
}
}
scaled_term(s, a, g, w).mul(&bracket, wp, RM)
}