use super::accuracy::{self, Bound};
use super::ball::Cache;
use super::wp::{RM, Wp};
use crate::base::arena::Arena;
use crate::base::bigcomplex::{Complex, c_abs, c_add, c_div, c_mul, c_sub};
use crate::base::errors::SymplexError;
use crate::base::libfn::LibFn;
use crate::base::node::ExprId;
use astro_float::BigFloat;
pub(super) fn takes(f: LibFn, args: &[ExprId], cache: Cache<'_>) -> bool {
let value = |i: usize| args.get(i).and_then(|a| cache.get(a));
match f {
LibFn::EllipticK | LibFn::EllipticE => {
value(0).is_some_and(|m| !m.1.is_zero() || above_one(&m.0))
}
LibFn::EllipticF => {
let (Some(phi), Some(m)) = (value(0), value(1)) else {
return false;
};
if !phi.1.is_zero() {
return false;
}
!m.1.is_zero() || (above_one(&m.0) && !real_f_applies(&phi.0, &m.0))
}
LibFn::EllipticPi => {
let (Some(n), Some(m)) = (value(0), value(1)) else {
return false;
};
!n.1.is_zero() || !m.1.is_zero() || above_one(&n.0) || above_one(&m.0)
}
_ => false,
}
}
pub(super) fn nonreal_args(f: LibFn, args: &[ExprId], cache: Cache<'_>) -> bool {
takes(f, args, cache)
&& args
.iter()
.any(|a| cache.get(a).is_some_and(|v| !v.1.is_zero()))
}
fn above_one(x: &BigFloat) -> bool {
let one = BigFloat::from_i32(1, 64);
x.cmp(&one).is_some_and(|c| c > 0)
}
fn real_f_applies(phi: &BigFloat, m: &BigFloat) -> bool {
let p = 128;
let mut cc = match astro_float::Consts::new() {
Ok(cc) => cc,
Err(_) => return false,
};
let half_pi = cc.pi(p, RM).div(&BigFloat::from_i32(2, p), p, RM);
if phi.abs().cmp(&half_pi).is_none_or(|c| c > 0) {
return false;
}
let s = phi.sin(p, RM, &mut cc);
let y = BigFloat::from_i32(1, p).sub(&m.mul(&s.mul(&s, p, RM), p, RM), p, RM);
crate::base::bigfloat::is_strictly_positive(&y) && y.exponent().is_some_and(|e| e > -100)
}
pub(super) fn eval(
f: LibFn,
args: &[ExprId],
cache: Cache<'_>,
w: &mut Wp<'_>,
) -> Result<Complex, SymplexError> {
let prec = w.bits();
let arg = |i: usize| -> Result<&Complex, SymplexError> {
args.get(i)
.and_then(|a| cache.get(a))
.ok_or_else(|| super::unevaluable("elliptic integral lost its argument"))
};
for i in 0..args.len() {
let a = arg(i)?;
if [&a.0, &a.1].iter().any(|x| x.is_nan() || x.is_inf()) {
return Err(super::unevaluable(format!(
"{} of special float value",
f.name()
)));
}
}
let mut extra = 32usize;
for _ in 0..4 {
let wp = prec + extra;
let (v, lost) = match f {
LibFn::EllipticK => (complete_k(arg(0)?, wp)?, 0),
LibFn::EllipticE => complete_e(arg(0)?, wp)?,
LibFn::EllipticF => incomplete_f(&arg(0)?.0, arg(1)?, &mut w.at(wp))?,
LibFn::EllipticPi => complete_pi(arg(0)?, arg(1)?, &mut w.at(wp))?,
_ => {
return Err(super::unevaluable(
"not a complete or incomplete elliptic integral",
));
}
};
let need = lost + part_gap(&v) + 16;
if need <= extra {
return Ok((super::round_to(v.0, prec), super::round_to(v.1, prec)));
}
if need > 4 * prec + 512 {
break;
}
extra = need + 16;
}
Err(super::unevaluable(format!(
"{}: the value cancels beyond the working precision",
f.name()
)))
}
fn part_gap(v: &Complex) -> usize {
let (Some(a), Some(b)) = (accuracy::part_mag(&v.0), accuracy::part_mag(&v.1)) else {
return 0;
};
usize::try_from((a - b).abs()).unwrap_or(usize::MAX / 8)
}
fn c_sqrt(z: &Complex, wp: usize) -> Complex {
let (a, b) = (&z.0, &z.1);
if b.is_zero() {
if a.is_zero() {
return (BigFloat::new(wp), BigFloat::new(wp));
}
if a.is_negative() {
return (BigFloat::new(wp), a.neg().sqrt(wp, RM));
}
return (a.sqrt(wp, RM), BigFloat::new(wp));
}
let r = c_abs(z, wp, RM);
let two = BigFloat::from_i32(2, wp);
if !a.is_negative() {
let s = r.add(a, wp, RM).div(&two, wp, RM).sqrt(wp, RM);
let t = b.div(&s.mul(&two, wp, RM), wp, RM);
(s, t)
} else {
let t = r.sub(a, wp, RM).div(&two, wp, RM).sqrt(wp, RM);
let s = b.abs().div(&t.mul(&two, wp, RM), wp, RM);
(s, if b.is_negative() { t.neg() } else { t })
}
}
fn real(c: i32, wp: usize) -> Complex {
(BigFloat::from_i32(c, wp), BigFloat::new(wp))
}
fn close(vals: &[&Complex], bits: usize, wp: usize) -> bool {
let first = vals[0];
let Some(scale) = accuracy::mag(first) else {
return vals.iter().all(|v| v.0.is_zero() && v.1.is_zero());
};
let limit = scale - i64::try_from(bits).unwrap_or(i64::MAX / 4);
vals[1..].iter().all(|v| {
let d = c_sub(v, first, wp, RM);
accuracy::mag(&d).is_none_or(|m| m < limit)
})
}
fn spread_bits(wp: usize) -> usize {
wp / 2 + 8
}
fn lambda(x: &Complex, y: &Complex, z: &Complex, wp: usize) -> (Complex, Complex) {
let (sx, sy, sz) = (c_sqrt(x, wp), c_sqrt(y, wp), c_sqrt(z, wp));
let l = c_add(
&c_add(&c_mul(&sx, &sy, wp, RM), &c_mul(&sy, &sz, wp, RM), wp, RM),
&c_mul(&sz, &sx, wp, RM),
wp,
RM,
);
(l, sz)
}
fn step(v: &Complex, l: &Complex, wp: usize) -> Complex {
let four = real(4, wp);
c_div(&c_add(v, l, wp, RM), &four, wp, RM)
}
fn rf(x: &Complex, y: &Complex, z: &Complex, wp: usize) -> Result<Complex, SymplexError> {
let (mut x, mut y, mut z) = (x.clone(), y.clone(), z.clone());
for _ in 0..(wp * 2 + 200) {
if close(&[&x, &y, &z], spread_bits(wp), wp) {
let mu = c_div(
&c_add(&c_add(&x, &y, wp, RM), &z, wp, RM),
&real(3, wp),
wp,
RM,
);
return Ok(c_div(&real(1, wp), &c_sqrt(&mu, wp), wp, RM));
}
let (l, _) = lambda(&x, &y, &z, wp);
x = step(&x, &l, wp);
y = step(&y, &l, wp);
z = step(&z, &l, wp);
}
Err(SymplexError::ComputationFailed {
operation: "evalf",
reason: "Carlson R_F did not converge".into(),
})
}
fn rd(x: &Complex, y: &Complex, z: &Complex, wp: usize) -> Result<Complex, SymplexError> {
let (mut x, mut y, mut z) = (x.clone(), y.clone(), z.clone());
let mut acc = real(0, wp);
let mut scale = real(1, wp);
let quarter = c_div(&real(1, wp), &real(4, wp), wp, RM);
for _ in 0..(wp * 2 + 200) {
if close(&[&x, &y, &z], spread_bits(wp), wp) {
let three_z = c_mul(&real(3, wp), &z, wp, RM);
let mu = c_div(
&c_add(&c_add(&x, &y, wp, RM), &three_z, wp, RM),
&real(5, wp),
wp,
RM,
);
let tail = c_div(&real(1, wp), &c_mul(&mu, &c_sqrt(&mu, wp), wp, RM), wp, RM);
return Ok(c_add(&acc, &c_mul(&scale, &tail, wp, RM), wp, RM));
}
let (l, sz) = lambda(&x, &y, &z, wp);
let term = c_div(
&real(3, wp),
&c_mul(&sz, &c_add(&z, &l, wp, RM), wp, RM),
wp,
RM,
);
acc = c_add(&acc, &c_mul(&scale, &term, wp, RM), wp, RM);
scale = c_mul(&scale, &quarter, wp, RM);
x = step(&x, &l, wp);
y = step(&y, &l, wp);
z = step(&z, &l, wp);
}
Err(SymplexError::ComputationFailed {
operation: "evalf",
reason: "Carlson R_D did not converge".into(),
})
}
fn one_minus(m: &Complex, wp: usize, what: &str) -> Result<Complex, SymplexError> {
let y = c_sub(&real(1, wp), m, wp, RM);
if y.0.is_zero() && y.1.is_zero() {
return Err(super::unevaluable(format!("{what}(1) is infinite")));
}
Ok(y)
}
fn complete_k(m: &Complex, wp: usize) -> Result<Complex, SymplexError> {
let y = one_minus(m, wp, "elliptic_k")?;
rf(&real(0, wp), &y, &real(1, wp), wp)
}
fn complete_e(m: &Complex, wp: usize) -> Result<(Complex, usize), SymplexError> {
let y = c_sub(&real(1, wp), m, wp, RM);
if y.0.is_zero() && y.1.is_zero() {
return Ok((real(1, wp), 0));
}
let (zero, one) = (real(0, wp), real(1, wp));
let f = rf(&zero, &y, &one, wp)?;
let d = rd(&zero, &y, &one, wp)?;
let t = c_div(&c_mul(m, &d, wp, RM), &real(3, wp), wp, RM);
let v = c_sub(&f, &t, wp, RM);
Ok((v.clone(), lost_bits(&[&f, &t], &v)))
}
fn lost_bits(parts: &[&Complex], v: &Complex) -> usize {
let top = parts.iter().filter_map(|p| accuracy::mag(p)).max();
match (top, accuracy::mag(v)) {
(Some(t), Some(m)) => usize::try_from(t - m).unwrap_or(0),
(Some(_), None) => usize::MAX / 8,
_ => 0,
}
}
fn incomplete_f(
phi: &BigFloat,
m: &Complex,
w: &mut Wp<'_>,
) -> Result<(Complex, usize), SymplexError> {
let wp = w.bits();
if phi.is_zero() {
return Ok((real(0, wp), 0));
}
if phi.is_nan() || phi.is_inf() {
return Err(super::unevaluable("elliptic_f of special float value"));
}
let k_bits = phi
.exponent()
.map_or(0, |e| usize::try_from(e.max(0)).unwrap_or(0));
let wr = wp + k_bits;
let pi = w.cc.pi(wr, RM).clone();
let k = phi
.div(&pi, wr, RM)
.add(&BigFloat::from_f64(0.5, wr), wr, RM)
.floor();
let phi0 = phi.sub(&k.mul(&pi, wr, RM), wr, RM);
let s = phi0.sin(wp, RM, w.cc);
let c = phi0.cos(wp, RM, w.cc);
let s2 = s.mul(&s, wp, RM);
let c2 = (c.mul(&c, wp, RM), BigFloat::new(wp));
let ms2 = c_mul(m, &(s2, BigFloat::new(wp)), wp, RM);
let y = c_sub(&real(1, wp), &ms2, wp, RM);
let r = rf(&c2, &y, &real(1, wp), wp)?;
let head = c_mul(&(s, BigFloat::new(wp)), &r, wp, RM);
if k.is_zero() {
return Ok((head, 0));
}
let kk = complete_k(m, wp)?;
let two_k = (k.mul(&BigFloat::from_i32(2, wp), wp, RM), BigFloat::new(wp));
let tail = c_mul(&two_k, &kk, wp, RM);
let v = c_add(&head, &tail, wp, RM);
Ok((v.clone(), lost_bits(&[&head, &tail], &v)))
}
fn is_one(x: &BigFloat) -> bool {
x.cmp(&BigFloat::from_i32(1, 64)) == Some(0)
}
fn complete_pi(n: &Complex, m: &Complex, w: &mut Wp<'_>) -> Result<(Complex, usize), SymplexError> {
if [&n.0, &n.1, &m.0, &m.1]
.iter()
.any(|x| x.is_nan() || x.is_inf())
{
return Err(super::unevaluable("elliptic_pi of special float value"));
}
pi_value(n, m, false, w)
}
fn pi_value(
n: &Complex,
m: &Complex,
above: bool,
w: &mut Wp<'_>,
) -> Result<(Complex, usize), SymplexError> {
let wp = w.bits();
let (n_real, m_real) = (n.1.is_zero(), m.1.is_zero());
if m_real && is_one(&m.0) {
return Err(super::unevaluable("elliptic_pi(n, 1) is infinite"));
}
if n_real && is_one(&n.0) {
return Err(super::unevaluable("elliptic_pi(1, m) is infinite"));
}
if m_real && above_one(&m.0) {
return pi_split(n, &m.0, w);
}
if n_real && m_real {
if above_one(&n.0) {
return pi_beyond_pole(&n.0, &m.0, above, w);
}
let v = super::arb_elliptic_pi(&n.0, &m.0, w)?;
return Ok(((v, BigFloat::new(wp)), 0));
}
if n_real && above_one(&n.0) {
return Err(super::unevaluable(
"elliptic_pi: a characteristic n > 1 with a non-real parameter m is not yet supported",
));
}
pi_principal(n, m, w)
}
fn pi_beyond_pole(
n: &BigFloat,
m: &BigFloat,
above: bool,
w: &mut Wp<'_>,
) -> Result<(Complex, usize), SymplexError> {
let wp = w.bits();
let k = super::arb_elliptic_k(m, w)?;
let p = super::arb_elliptic_pi(&m.div(n, wp, RM), m, w)?;
let re = k.sub(&p, wp, RM);
let one = BigFloat::from_i32(1, wp);
let q = n.div(&n.sub(&one, wp, RM).mul(&n.sub(m, wp, RM), wp, RM), wp, RM);
let half_pi = w.cc.pi(wp, RM).div(&BigFloat::from_i32(2, wp), wp, RM);
let im = half_pi.mul(&q.sqrt(wp, RM), wp, RM);
let im = if above { im } else { im.neg() };
let zero = BigFloat::new(wp);
let lost = lost_bits(
&[&(k, zero.clone()), &(p, zero.clone())],
&(re.clone(), zero),
);
Ok(((re, im), lost))
}
fn pi_split(n: &Complex, m: &BigFloat, w: &mut Wp<'_>) -> Result<(Complex, usize), SymplexError> {
let wp = w.bits();
let zero = BigFloat::new(wp);
if n.1.is_zero() && n.0.cmp(m) == Some(0) {
let (e, lost) = complete_e(n, wp)?;
let one_minus_m = c_sub(&real(1, wp), n, wp, RM);
return Ok((c_div(&e, &one_minus_m, wp, RM), lost));
}
let one = BigFloat::from_i32(1, wp);
let inv_m = one.div(m, wp, RM);
let n_over_m = (n.0.mul(&inv_m, wp, RM), n.1.mul(&inv_m, wp, RM));
let (p1, lost1) = pi_value(&n_over_m, &(inv_m.clone(), zero.clone()), false, w)?;
let m_minus_one = (m.sub(&one, wp, RM), zero.clone());
let n_minus_one = c_sub(n, &real(1, wp), wp, RM);
let n1 = c_div(
&c_mul(n, &m_minus_one, wp, RM),
&c_mul(&(m.clone(), zero.clone()), &n_minus_one, wp, RM),
wp,
RM,
);
let m1 = (one.sub(&inv_m, wp, RM), zero.clone());
let n_on_cut_above = n.1.is_zero() && above_one(&n.0);
let (p2, lost2) = pi_value(&n1, &m1, n_on_cut_above, w)?;
let root_m = m.sqrt(wp, RM);
let a = (p1.0.div(&root_m, wp, RM), p1.1.div(&root_m, wp, RM));
let one_minus_n = c_sub(&real(1, wp), n, wp, RM);
let c = (
one_minus_n.0.mul(&root_m, wp, RM),
one_minus_n.1.mul(&root_m, wp, RM),
);
let q = c_div(&p2, &c, wp, RM);
let b = (q.1, q.0.neg());
let v = c_add(&a, &b, wp, RM);
let lost = lost1.max(lost2).max(lost_bits(&[&a, &b], &v));
Ok((v, lost))
}
fn pi_principal(
n: &Complex,
m: &Complex,
w: &mut Wp<'_>,
) -> Result<(Complex, usize), SymplexError> {
let wp = w.bits();
let y = c_sub(&real(1, wp), m, wp, RM);
let p = c_sub(&real(1, wp), n, wp, RM);
if y.0.is_zero() && y.1.is_zero() {
return Err(super::unevaluable("elliptic_pi(n, 1) is infinite"));
}
if p.0.is_zero() && p.1.is_zero() {
return Err(super::unevaluable("elliptic_pi(1, m) is infinite"));
}
let Some(q) = rotation(&[&y, &p], wp) else {
return Err(super::unevaluable(
"elliptic_pi: the arguments 1 - m, 1 and 1 - n lie in no half-plane: not yet supported",
));
};
let (y, z, p) = (c_mul(&y, &q, wp, RM), q.clone(), c_mul(&p, &q, wp, RM));
let x = real(0, wp);
let sq = c_sqrt(&q, wp);
let sq3 = c_mul(&c_mul(&sq, &sq, wp, RM), &sq, wp, RM);
let f = c_mul(&sq, &rf(&x, &y, &z, wp)?, wp, RM);
let j = c_mul(&sq3, &rj(&x, &y, &z, &p, w)?, wp, RM);
let t = c_div(&c_mul(n, &j, wp, RM), &real(3, wp), wp, RM);
let v = c_add(&f, &t, wp, RM);
let lost = lost_bits(&[&f, &t], &v);
Ok((v, lost))
}
fn rotation(ws: &[&Complex], wp: usize) -> Option<Complex> {
if ws
.iter()
.all(|z| crate::base::bigfloat::is_strictly_positive(&z.0))
{
return Some(real(1, wp));
}
let (mut lo, mut hi) = (0.0f64, 0.0f64);
for z in ws {
let a = angle(z)?;
lo = lo.min(a);
hi = hi.max(a);
}
if hi - lo > std::f64::consts::PI - 0.2 {
return None;
}
let theta = (lo + hi) / 2.0;
let (s, c) = theta.sin_cos();
Some((
crate::base::bigfloat::from_f64(c, wp),
crate::base::bigfloat::from_f64(-s, wp),
))
}
fn angle(z: &Complex) -> Option<f64> {
let e = match (accuracy::part_mag(&z.0), accuracy::part_mag(&z.1)) {
(Some(a), Some(b)) => a.max(b),
(a, b) => a.or(b)?,
};
let scaled = |x: &BigFloat| -> f64 {
if x.is_zero() {
return 0.0;
}
let mut y = x.clone();
let k = x.exponent().map_or(0, i64::from) - e;
y.set_exponent(i32::try_from(k).unwrap_or(i32::MIN / 2));
crate::base::bigfloat::to_f64(&y).unwrap_or(0.0)
};
Some(scaled(&z.1).atan2(scaled(&z.0)))
}
fn rj(
x: &Complex,
y: &Complex,
z: &Complex,
p: &Complex,
w: &mut Wp<'_>,
) -> Result<Complex, SymplexError> {
let wp = w.bits();
let delta = c_mul(
&c_mul(&c_sub(p, x, wp, RM), &c_sub(p, y, wp, RM), wp, RM),
&c_sub(p, z, wp, RM),
wp,
RM,
);
let (mut x, mut y, mut z, mut p) = (x.clone(), y.clone(), z.clone(), p.clone());
let mut sum = real(0, wp);
let mut scale = BigFloat::from_i32(1, wp);
for _ in 0..(wp * 2 + 200) {
if close(&[&x, &y, &z, &p], spread_bits(wp), wp) {
let two_p = c_add(&p, &p, wp, RM);
let mu = c_div(
&c_add(&c_add(&c_add(&x, &y, wp, RM), &z, wp, RM), &two_p, wp, RM),
&real(5, wp),
wp,
RM,
);
let tail = c_div(&real(1, wp), &c_mul(&mu, &c_sqrt(&mu, wp), wp, RM), wp, RM);
let tail = (tail.0.mul(&scale, wp, RM), tail.1.mul(&scale, wp, RM));
let six = real(6, wp);
return Ok(c_add(&c_mul(&six, &sum, wp, RM), &tail, wp, RM));
}
let (l, sz) = lambda(&x, &y, &z, wp);
let (sx, sy, sp) = (c_sqrt(&x, wp), c_sqrt(&y, wp), c_sqrt(&p, wp));
let d = c_mul(
&c_mul(&c_add(&sp, &sx, wp, RM), &c_add(&sp, &sy, wp, RM), wp, RM),
&c_add(&sp, &sz, wp, RM),
wp,
RM,
);
let s3 = scale.mul(&scale, wp, RM).mul(&scale, wp, RM);
let e = c_div(
&(delta.0.mul(&s3, wp, RM), delta.1.mul(&s3, wp, RM)),
&c_mul(&d, &d, wp, RM),
wp,
RM,
);
let term = c_div(&rc_one(&e, w)?, &d, wp, RM);
sum = c_add(
&sum,
&(term.0.mul(&scale, wp, RM), term.1.mul(&scale, wp, RM)),
wp,
RM,
);
x = step(&x, &l, wp);
y = step(&y, &l, wp);
z = step(&z, &l, wp);
p = step(&p, &l, wp);
scale = scale.div(&BigFloat::from_i32(4, wp), wp, RM);
}
Err(SymplexError::ComputationFailed {
operation: "evalf",
reason: "Carlson R_J did not converge".into(),
})
}
fn rc_one(e: &Complex, w: &mut Wp<'_>) -> Result<Complex, SymplexError> {
let wp = w.bits();
let Some(k) = accuracy::mag(e) else {
return Ok(real(1, wp));
};
if k <= -2 {
let neg_e = (e.0.neg(), e.1.neg());
let mut power = real(1, wp);
let mut acc = real(1, wp);
let limit = -i64::try_from(wp).unwrap_or(i64::MAX / 4) - 8;
for j in 1..(4 * wp + 64) {
power = c_mul(&power, &neg_e, wp, RM);
let odd = BigFloat::from_i64(2 * j as i64 + 1, wp);
acc = c_add(
&acc,
&(power.0.div(&odd, wp, RM), power.1.div(&odd, wp, RM)),
wp,
RM,
);
if accuracy::mag(&power).is_none_or(|m| m < limit) {
return Ok(acc);
}
}
return Err(SymplexError::ComputationFailed {
operation: "evalf",
reason: "Carlson R_C series did not converge".into(),
});
}
if k >= 1 {
return Err(super::unevaluable(
"Carlson R_J: an argument off its domain",
));
}
let s = c_sqrt(e, wp);
let a = super::c_atan(&s, w);
Ok(c_div(&a, &s, wp, RM))
}
pub(super) fn bound(
arena: &Arena,
id: ExprId,
args: &[ExprId],
v: &Complex,
cache: Cache<'_>,
w: &mut Wp<'_>,
) -> Bound {
let prec = w.bits();
if [&v.0, &v.1].iter().any(|x| x.is_nan() || x.is_inf()) {
return Bound::UNKNOWN;
}
let joint = accuracy::lg_abs(v) - prec as f64 - 12.0;
let own = |x: &BigFloat| {
if x.is_zero() {
joint
} else {
accuracy::part_lg(x) - prec as f64 - 12.0
}
};
let mut total = accuracy::EXACT;
let last = args.len().saturating_sub(1);
for (i, a) in args.iter().enumerate() {
let (Some(val), Some(b)) = (cache.get(a), cache.bound(a)) else {
return Bound::UNKNOWN;
};
if b.is_unknown() {
return Bound::UNKNOWN;
}
if i == last && on_cut_undecided(val, b) {
return Bound::UNKNOWN;
}
if b.is_exact() || args[..i].contains(a) {
continue;
}
match super::sensitivity::numeric_change(arena, id, *a, b.joint(), v, cache, w) {
Some(c) => total = accuracy::lsum(total, c),
None => return Bound::UNKNOWN,
}
}
if accuracy::is_unknown(total) {
return Bound::UNKNOWN;
}
Bound {
re: accuracy::lsum(own(&v.0), total),
im: accuracy::lsum(own(&v.1), total),
}
}
fn on_cut_undecided(m: &Complex, b: Bound) -> bool {
if accuracy::exact_zero(&m.1, b.im) || !accuracy::part_contains_zero(&m.1, b.im) {
return false;
}
let p = m.0.mantissa_max_bit_len().unwrap_or(64).max(64) + 64;
let d = m.0.sub(&BigFloat::from_i32(1, 64), p, RM);
!d.is_negative() || accuracy::part_contains_zero(&d, b.re)
}