use astro_float::{BigFloat, Consts, RoundingMode};
use crate::base::errors::SymplexError;
use crate::base::numeric::Q;
pub(crate) fn even(k: usize) -> Q {
crate::base::bernoulli::even(k)
}
pub(super) fn bernoulli_function(
s: &BigFloat,
prec: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<BigFloat, SymplexError> {
if s.is_nan() || s.is_inf() {
return Err(SymplexError::Unevaluable {
reason: "bernoulli of a non-finite argument".into(),
});
}
if s.is_zero() {
return Ok(BigFloat::from_i32(1, prec));
}
if *s == BigFloat::from_i32(1, 64) {
return Ok(BigFloat::from_i32(-1, prec).div(&BigFloat::from_i32(2, prec), prec, rm));
}
let wp = prec + 32;
let one_minus_s = BigFloat::from_i32(1, 64).sub(s, super::exact_bits(s, wp), rm);
let zeta = super::arb_zeta(&one_minus_s, wp, rm, cc)?;
let r = s.mul(&zeta, wp, rm).neg();
Ok(super::round_to(r, prec, rm))
}
const MAX_EULER_ORDER: f64 = 1.0e6;
pub(super) fn euler_function(
s: &BigFloat,
prec: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<BigFloat, SymplexError> {
let unevaluable = |reason: &str| SymplexError::Unevaluable {
reason: reason.into(),
};
if s.is_nan() || s.is_inf() {
return Err(unevaluable("euler_number of a non-finite argument"));
}
let s_f = super::bigfloat_to_f64(s, rm, cc)?;
if s_f.abs() > MAX_EULER_ORDER {
return Err(unevaluable(
"euler_number of an argument beyond 10^6 is not evaluated numerically",
));
}
if s.is_zero() {
return Ok(BigFloat::from_i32(1, prec));
}
if s.is_int() && !s.is_negative() {
let n = s_f as usize;
if n % 2 == 1 {
return Ok(BigFloat::new(prec));
}
if n <= crate::base::bernoulli::EXACT_EULER_N {
let e = crate::base::combinatorics::euler_even(n / 2);
return Ok(super::round_to(
crate::base::numeric::bigint_to_bigfloat(&e, prec + 8),
prec,
rm,
));
}
}
if *s == BigFloat::from_i32(-1, 64) {
let pi = cc.pi(prec + 8, rm).clone();
return Ok(super::round_to(
pi.div(&BigFloat::from_i32(2, 64), prec + 8, rm),
prec,
rm,
));
}
let cap = super::cancellation_cap(prec);
let size = s_f.abs() * (s_f.abs().ln().max(0.0) + 2.0) + 2.0;
let mut extra = (32 + super::magnitude_bits(size)).min(cap);
loop {
let wp = prec + extra;
let (v, largest) = if s_f >= 0.5 {
euler_positive(s, wp, rm, cc)?
} else {
euler_negative(s, wp, rm, cc)?
};
let lv = lg(&v) - 1.0;
let lost = if lv.is_finite() && largest.is_finite() {
((largest - lv).max(0.0).ceil() as usize).min(cap + 1)
} else {
wp
};
if lost + 24 <= extra {
return Ok(super::round_to(v, prec, rm));
}
if extra >= cap || lost + 24 > cap {
return Err(super::special_exhausted(prec));
}
extra = (lost + 40).max(2 * extra).min(cap);
}
}
fn lg(x: &BigFloat) -> f64 {
if x.is_zero() {
return f64::NEG_INFINITY;
}
x.exponent().map_or(f64::NEG_INFINITY, f64::from)
}
fn euler_positive(
s: &BigFloat,
wp: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<(BigFloat, f64), SymplexError> {
let one = BigFloat::from_i32(1, 64);
let x = s.add(&one, super::exact_bits(s, wp), rm);
let cos = super::sin_half_pi(&x, wp, rm, cc);
if cos.is_zero() {
return Ok((BigFloat::new(wp), f64::NEG_INFINITY));
}
let quarter = BigFloat::from_f64(0.25, 64);
let three_quarters = BigFloat::from_f64(0.75, 64);
let (h1, l1) = super::polylog::hurwitz_real(&x, &quarter, wp, rm, cc)?;
let (h2, l2) = super::polylog::hurwitz_real(&x, &three_quarters, wp, rm, cc)?;
let lw = wp + 16;
let two_pi = cc
.pi(lw, rm)
.clone()
.mul(&BigFloat::from_i32(2, 64), lw, rm);
let ln_gamma = super::stirling_log_gamma(&x, lw, rm, cc)?;
let scale = ln_gamma
.sub(&x.mul(&two_pi.ln(lw, rm, cc), lw, rm), lw, rm)
.exp(wp, rm, cc)
.mul(&cos, wp, rm)
.mul(&BigFloat::from_i32(2, 64), wp, rm);
let diff = h1.sub(&h2, wp, rm);
Ok((scale.mul(&diff, wp, rm), lg(&scale) + l1.max(l2)))
}
fn euler_negative(
s: &BigFloat,
wp: usize,
rm: RoundingMode,
cc: &mut Consts,
) -> Result<(BigFloat, f64), SymplexError> {
let exact = super::exact_bits(s, wp);
let sigma = s.neg();
let half = BigFloat::from_f64(0.5, 64);
let three_quarters = BigFloat::from_f64(0.75, 64);
let (h1, l1) = super::polylog::hurwitz_real(&sigma, &half, wp, rm, cc)?;
let (h2, l2) = super::polylog::hurwitz_real(&sigma, &three_quarters, wp, rm, cc)?;
let s1 = s.add(&BigFloat::from_i32(1, 64), exact, rm);
let p = super::bf_pow(&BigFloat::from_i32(2, 64), &s1, wp + 16, rm, cc);
let lp = lg(&p);
let inner = h1.sub(&p.mul(&h2, wp, rm), wp, rm);
Ok((p.mul(&inner, wp, rm), lp + l1.max(lp + l2)))
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
use num_rational::Ratio;
#[test]
fn first_values() {
let q = |n: i64, d: i64| Ratio::new(BigInt::from(n), BigInt::from(d));
assert_eq!(even(1), q(1, 6));
assert_eq!(even(2), q(-1, 30));
assert_eq!(even(3), q(1, 42));
assert_eq!(even(6), q(691, -2730));
}
}