use core::cmp::Ordering;
use core::fmt;
use core::str::FromStr;
use alloc::string::{String, ToString};
use crate::error::{Error, Result};
use crate::int::{Int, Sign};
use crate::nat::Nat;
use crate::rational::Rational;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum RoundingMode {
#[default]
Nearest,
TowardZero,
TowardPositive,
TowardNegative,
AwayFromZero,
}
#[derive(Clone, PartialEq, Eq)]
enum Repr {
NaN,
Inf(bool),
Zero(bool),
Normal { neg: bool, sig: Nat, exp: i64 },
}
#[derive(Clone)]
pub struct Float {
repr: Repr,
precision: u64,
}
impl Float {
pub fn nan(precision: u64) -> Float {
Float {
repr: Repr::NaN,
precision: precision.max(1),
}
}
pub fn infinity(precision: u64) -> Float {
Float {
repr: Repr::Inf(false),
precision: precision.max(1),
}
}
pub fn neg_infinity(precision: u64) -> Float {
Float {
repr: Repr::Inf(true),
precision: precision.max(1),
}
}
pub fn zero(precision: u64) -> Float {
Float {
repr: Repr::Zero(false),
precision: precision.max(1),
}
}
pub fn neg_zero(precision: u64) -> Float {
Float {
repr: Repr::Zero(true),
precision: precision.max(1),
}
}
fn zero_signed(neg: bool, precision: u64) -> Float {
Float {
repr: Repr::Zero(neg),
precision: precision.max(1),
}
}
fn inf_signed(neg: bool, precision: u64) -> Float {
Float {
repr: Repr::Inf(neg),
precision: precision.max(1),
}
}
fn round_raw(
neg: bool,
mant: Nat,
exp: i64,
precision: u64,
mode: RoundingMode,
) -> (Float, Ordering) {
let precision = precision.max(1);
if mant.is_zero() {
return (Float::zero_signed(neg, precision), Ordering::Equal);
}
let bits = mant.bit_len();
if bits <= precision {
let shift = precision - bits;
let repr = Repr::Normal {
neg,
sig: mant.shl(shift),
exp: exp - shift as i64,
};
return (Float { repr, precision }, Ordering::Equal);
}
let drop = bits - precision;
let low = mant.low_bits(drop);
let mut hi = mant.shr(drop);
let mut new_exp = exp + drop as i64;
let half = Nat::one().shl(drop - 1);
let inexact = !low.is_zero();
let round_up = match mode {
RoundingMode::TowardZero => false,
RoundingMode::AwayFromZero => inexact,
RoundingMode::TowardPositive => !neg && inexact,
RoundingMode::TowardNegative => neg && inexact,
RoundingMode::Nearest => match low.cmp(&half) {
Ordering::Greater => true,
Ordering::Less => false,
Ordering::Equal => !hi.is_even(),
},
};
if round_up {
hi = hi.add(&Nat::one());
if hi.bit_len() > precision {
hi = hi.shr(1);
new_exp += 1;
}
}
let ternary = if !inexact {
Ordering::Equal
} else if round_up != neg {
Ordering::Greater
} else {
Ordering::Less
};
let repr = Repr::Normal {
neg,
sig: hi,
exp: new_exp,
};
(Float { repr, precision }, ternary)
}
pub fn from_int(n: &Int, precision: u64, mode: RoundingMode) -> Float {
Float::round_raw(n.is_negative(), n.magnitude(), 0, precision, mode).0
}
pub fn from_rational(r: &Rational, precision: u64, mode: RoundingMode) -> Float {
if r.is_zero() {
return Float::zero(precision);
}
let num = r.numerator();
let den = r.denominator();
let work_num = num.magnitude().bit_len().max(1);
let work_den = den.magnitude().bit_len().max(1);
let fnum = Float::from_int(num, work_num, RoundingMode::TowardZero);
let fden = Float::from_int(den, work_den, RoundingMode::TowardZero);
fnum.div(&fden, precision, mode)
}
pub fn from_f64(x: f64, precision: u64, mode: RoundingMode) -> Float {
let bits = x.to_bits();
let neg = bits >> 63 == 1;
let exp_field = ((bits >> 52) & 0x7ff) as i64;
let frac = bits & 0x000f_ffff_ffff_ffff;
if exp_field == 0x7ff {
return if frac == 0 {
Float::inf_signed(neg, precision)
} else {
Float::nan(precision)
};
}
let (mantissa, exponent) = if exp_field == 0 {
if frac == 0 {
return Float::zero_signed(neg, precision);
}
(frac, -1074) } else {
((1u64 << 52) | frac, exp_field - 1075)
};
Float::round_raw(neg, Nat::from_u64(mantissa), exponent, precision, mode).0
}
pub fn from_f32(x: f32, precision: u64, mode: RoundingMode) -> Float {
Float::from_f64(x as f64, precision, mode)
}
pub fn round(&self, precision: u64, mode: RoundingMode) -> Float {
self.round_impl(precision, mode).0
}
pub fn floor(&self) -> Option<Int> {
self.to_rational().map(|r| r.floor())
}
pub fn ceil(&self) -> Option<Int> {
self.to_rational().map(|r| r.ceil())
}
pub fn trunc(&self) -> Option<Int> {
self.to_rational().map(|r| r.trunc())
}
pub fn round_to_int(&self) -> Option<Int> {
self.to_rational().map(|r| r.round())
}
fn round_impl(&self, precision: u64, mode: RoundingMode) -> (Float, Ordering) {
match &self.repr {
Repr::NaN => (Float::nan(precision), Ordering::Equal),
Repr::Inf(neg) => (Float::inf_signed(*neg, precision), Ordering::Equal),
Repr::Zero(neg) => (Float::zero_signed(*neg, precision), Ordering::Equal),
Repr::Normal { neg, sig, exp } => {
Float::round_raw(*neg, sig.clone(), *exp, precision, mode)
}
}
}
pub fn neg(&self) -> Float {
let repr = match &self.repr {
Repr::NaN => Repr::NaN,
Repr::Inf(neg) => Repr::Inf(!neg),
Repr::Zero(neg) => Repr::Zero(!neg),
Repr::Normal { neg, sig, exp } => Repr::Normal {
neg: !neg,
sig: sig.clone(),
exp: *exp,
},
};
Float {
repr,
precision: self.precision,
}
}
pub fn abs(&self) -> Float {
let repr = match &self.repr {
Repr::NaN => Repr::NaN,
Repr::Inf(_) => Repr::Inf(false),
Repr::Zero(_) => Repr::Zero(false),
Repr::Normal { sig, exp, .. } => Repr::Normal {
neg: false,
sig: sig.clone(),
exp: *exp,
},
};
Float {
repr,
precision: self.precision,
}
}
pub fn add(&self, rhs: &Float, precision: u64, mode: RoundingMode) -> Float {
self.add_ternary(rhs, precision, mode).0
}
pub fn add_ternary(
&self,
rhs: &Float,
precision: u64,
mode: RoundingMode,
) -> (Float, Ordering) {
use Repr::*;
match (&self.repr, &rhs.repr) {
(NaN, _) | (_, NaN) => (Float::nan(precision), Ordering::Equal),
(Inf(a), Inf(b)) => {
if a == b {
(Float::inf_signed(*a, precision), Ordering::Equal)
} else {
(Float::nan(precision), Ordering::Equal)
}
}
(Inf(a), _) => (Float::inf_signed(*a, precision), Ordering::Equal),
(_, Inf(b)) => (Float::inf_signed(*b, precision), Ordering::Equal),
(Zero(a), Zero(b)) => {
let neg = if a == b {
*a
} else {
mode == RoundingMode::TowardNegative
};
(Float::zero_signed(neg, precision), Ordering::Equal)
}
(Zero(_), _) => rhs.round_impl(precision, mode),
(_, Zero(_)) => self.round_impl(precision, mode),
(Normal { .. }, Normal { .. }) => self.add_normal(rhs, precision, mode),
}
}
fn add_normal(&self, rhs: &Float, precision: u64, mode: RoundingMode) -> (Float, Ordering) {
let (
Repr::Normal {
neg: na,
sig: sa,
exp: ea,
},
Repr::Normal {
neg: nb,
sig: sb,
exp: eb,
},
) = (&self.repr, &rhs.repr)
else {
unreachable!("add_normal called on non-normal operands")
};
let emin = (*ea).min(*eb);
let a = Int::from_sign_magnitude(sign_of(*na), sa.shl((*ea - emin) as u64));
let b = Int::from_sign_magnitude(sign_of(*nb), sb.shl((*eb - emin) as u64));
let s = a.add(&b);
if s.is_zero() {
let neg = mode == RoundingMode::TowardNegative;
(Float::zero_signed(neg, precision), Ordering::Equal)
} else {
Float::round_raw(s.is_negative(), s.magnitude(), emin, precision, mode)
}
}
pub fn sub(&self, rhs: &Float, precision: u64, mode: RoundingMode) -> Float {
self.add(&rhs.neg(), precision, mode)
}
pub fn sub_ternary(
&self,
rhs: &Float,
precision: u64,
mode: RoundingMode,
) -> (Float, Ordering) {
self.add_ternary(&rhs.neg(), precision, mode)
}
pub fn mul(&self, rhs: &Float, precision: u64, mode: RoundingMode) -> Float {
self.mul_ternary(rhs, precision, mode).0
}
pub fn mul_ternary(
&self,
rhs: &Float,
precision: u64,
mode: RoundingMode,
) -> (Float, Ordering) {
use Repr::*;
match (&self.repr, &rhs.repr) {
(NaN, _) | (_, NaN) => (Float::nan(precision), Ordering::Equal),
(Inf(_), Zero(_)) | (Zero(_), Inf(_)) => (Float::nan(precision), Ordering::Equal),
(Inf(a), other) | (other, Inf(a)) => (
Float::inf_signed(*a ^ other.sign_bit(), precision),
Ordering::Equal,
),
(Zero(a), other) | (other, Zero(a)) => (
Float::zero_signed(*a ^ other.sign_bit(), precision),
Ordering::Equal,
),
(
Normal {
neg: na,
sig: sa,
exp: ea,
},
Normal {
neg: nb,
sig: sb,
exp: eb,
},
) => {
let (sa, ea) = strip_pow2(sa, *ea);
let (sb, eb) = strip_pow2(sb, *eb);
Float::round_raw(na ^ nb, sa.mul(&sb), ea + eb, precision, mode)
}
}
}
pub fn div(&self, rhs: &Float, precision: u64, mode: RoundingMode) -> Float {
self.div_ternary(rhs, precision, mode).0
}
pub fn div_ternary(
&self,
rhs: &Float,
precision: u64,
mode: RoundingMode,
) -> (Float, Ordering) {
use Repr::*;
match (&self.repr, &rhs.repr) {
(NaN, _) | (_, NaN) => (Float::nan(precision), Ordering::Equal),
(Inf(_), Inf(_)) | (Zero(_), Zero(_)) => (Float::nan(precision), Ordering::Equal),
(Inf(a), other) => (
Float::inf_signed(*a ^ other.sign_bit(), precision),
Ordering::Equal,
),
(other, Inf(b)) => (
Float::zero_signed(other.sign_bit() ^ *b, precision),
Ordering::Equal,
),
(other, Zero(b)) => {
(
Float::inf_signed(other.sign_bit() ^ *b, precision),
Ordering::Equal,
)
}
(Zero(a), other) => (
Float::zero_signed(*a ^ other.sign_bit(), precision),
Ordering::Equal,
),
(
Normal {
neg: na,
sig: sa,
exp: ea,
},
Normal {
neg: nb,
sig: sb,
exp: eb,
},
) => {
let (sa, ea) = strip_pow2(sa, *ea);
let (sb, eb) = strip_pow2(sb, *eb);
let (sa, sb) = (sa.as_ref(), sb.as_ref());
let guard = (precision as i64 + 2 + sb.bit_len() as i64 - sa.bit_len() as i64)
.max(2) as u64;
let num = sa.shl(guard);
let (mut q, r) = num.div_rem(sb).expect("divisor is non-zero");
if !r.is_zero() && q.is_even() {
q = q.add(&Nat::one());
}
Float::round_raw(na ^ nb, q, ea - eb - guard as i64, precision, mode)
}
}
}
pub fn sqrt(&self, precision: u64, mode: RoundingMode) -> Float {
self.sqrt_ternary(precision, mode).0
}
pub fn sqrt_ternary(&self, precision: u64, mode: RoundingMode) -> (Float, Ordering) {
match &self.repr {
Repr::NaN => (Float::nan(precision), Ordering::Equal),
Repr::Inf(true) => (Float::nan(precision), Ordering::Equal),
Repr::Inf(false) => (Float::infinity(precision), Ordering::Equal),
Repr::Zero(neg) => (Float::zero_signed(*neg, precision), Ordering::Equal),
Repr::Normal { neg: true, .. } => (Float::nan(precision), Ordering::Equal),
Repr::Normal {
neg: false,
sig,
exp,
} => {
let mut s = sig.clone();
let mut e = *exp;
if e & 1 != 0 {
s = s.shl(1);
e -= 1;
}
let want = 2 * (precision + 2);
let cur = s.bit_len();
let mut shift = want.saturating_sub(cur);
if shift & 1 != 0 {
shift += 1;
}
let radicand = s.shl(shift);
let mut m = radicand.isqrt();
if m.mul(&m) != radicand && m.is_even() {
m = m.add(&Nat::one());
}
Float::round_raw(false, m, e / 2 - (shift / 2) as i64, precision, mode)
}
}
}
#[inline]
pub fn is_nan(&self) -> bool {
matches!(self.repr, Repr::NaN)
}
#[inline]
pub fn is_infinite(&self) -> bool {
matches!(self.repr, Repr::Inf(_))
}
#[inline]
pub fn is_finite(&self) -> bool {
matches!(self.repr, Repr::Zero(_) | Repr::Normal { .. })
}
#[inline]
pub fn is_zero(&self) -> bool {
matches!(self.repr, Repr::Zero(_))
}
#[inline]
pub fn is_sign_negative(&self) -> bool {
self.repr.sign_bit()
}
pub fn sign(&self) -> Sign {
match &self.repr {
Repr::Normal { neg, .. } | Repr::Inf(neg) => sign_of(*neg),
_ => Sign::Zero,
}
}
#[inline]
pub fn precision(&self) -> u64 {
self.precision
}
pub fn exponent(&self) -> Option<i64> {
match &self.repr {
Repr::Normal { exp, .. } => Some(*exp),
_ => None,
}
}
pub fn significand(&self) -> Option<&Nat> {
match &self.repr {
Repr::Normal { sig, .. } => Some(sig),
_ => None,
}
}
pub fn to_rational(&self) -> Option<Rational> {
match &self.repr {
Repr::Zero(_) => Some(Rational::ZERO),
Repr::Normal { neg, sig, exp } => {
let sign = sign_of(*neg);
Some(if *exp >= 0 {
Rational::from_integer(Int::from_sign_magnitude(sign, sig.shl(*exp as u64)))
} else {
let num = Int::from_sign_magnitude(sign, sig.clone());
let den = Int::ONE.mul_2k((-exp) as u32);
Rational::new(num, den)
})
}
_ => None,
}
}
pub fn to_f64(&self) -> f64 {
match &self.repr {
Repr::NaN => f64::NAN,
Repr::Inf(neg) => {
if *neg {
f64::NEG_INFINITY
} else {
f64::INFINITY
}
}
Repr::Zero(neg) => {
if *neg {
-0.0
} else {
0.0
}
}
Repr::Normal { neg, sig, exp } => {
let mant = Int::from(sig.clone()).to_f64();
let scaled = mant * exp2(*exp);
if *neg { -scaled } else { scaled }
}
}
}
pub fn to_f32(&self) -> f32 {
self.to_f64() as f32
}
pub fn to_shortest_string(&self) -> String {
match &self.repr {
Repr::NaN => return String::from("NaN"),
Repr::Inf(true) => return String::from("-inf"),
Repr::Inf(false) => return String::from("inf"),
Repr::Zero(_) => return String::from("0"),
Repr::Normal { .. } => {}
}
let value = self.to_rational().expect("finite non-zero");
let abs = value.abs();
let ten = Rational::from(Int::from_i64(10));
let one = Rational::ONE;
let mut e10 = 0i64;
let mut v = abs;
while v >= ten {
v = v.div(&ten);
e10 += 1;
}
while v < one {
v = v.mul(&ten);
e10 -= 1;
}
let max_digits = (self.precision as usize) / 3 + 4;
for d in 1..=max_digits {
let scale = Rational::from(Int::from_i64(10).pow((d - 1) as u32));
let scaled = v.mul(&scale);
let m = scaled.add(&Rational::power_of_two(-1)).floor();
let mut ds = m.to_string();
let exp = e10 + (ds.len() as i64 - d as i64);
while ds.len() > 1 && ds.ends_with('0') {
ds.pop();
}
let candidate = format_plain(self.is_sign_negative(), &ds, exp);
if let Ok(r) = candidate.parse::<Rational>()
&& Float::from_rational(&r, self.precision, RoundingMode::Nearest) == *self
{
return candidate;
}
}
self.to_decimal_string(max_digits as u32)
}
pub fn to_exact_string(&self) -> String {
match &self.repr {
Repr::NaN => alloc::format!("nan@{}", self.precision),
Repr::Inf(neg) => {
alloc::format!("{}inf@{}", if *neg { "-" } else { "" }, self.precision)
}
Repr::Zero(neg) => {
alloc::format!("{}0@{}", if *neg { "-" } else { "" }, self.precision)
}
Repr::Normal { neg, sig, exp } => alloc::format!(
"{}{sig}p{exp}@{}",
if *neg { "-" } else { "" },
self.precision
),
}
}
pub fn from_exact_string(s: &str) -> Result<Float> {
let (body, prec_s) = s.rsplit_once('@').ok_or(Error::Parse)?;
let precision: u64 = prec_s.parse().map_err(|_| Error::Parse)?;
let (neg, rest) = match body.strip_prefix('-') {
Some(r) => (true, r),
None => (false, body),
};
if rest.eq_ignore_ascii_case("nan") {
return Ok(Float::nan(precision));
}
if rest.eq_ignore_ascii_case("inf") {
return Ok(Float::inf_signed(neg, precision));
}
if rest == "0" {
return Ok(Float::zero_signed(neg, precision));
}
let (sig_s, exp_s) = rest.split_once('p').ok_or(Error::Parse)?;
let sig = Nat::from_str(sig_s)?;
let exp: i64 = exp_s.parse().map_err(|_| Error::Parse)?;
Ok(Float::round_raw(neg, sig, exp, precision, RoundingMode::Nearest).0)
}
pub fn to_decimal_string(&self, frac_digits: u32) -> String {
match &self.repr {
Repr::NaN => String::from("NaN"),
Repr::Inf(true) => String::from("-inf"),
Repr::Inf(false) => String::from("inf"),
_ => {
let r = self.to_rational().expect("finite");
let mut out = String::new();
let _ = r.write_decimal(&mut out, frac_digits, false);
out
}
}
}
}
fn format_plain(neg: bool, ds: &str, exp: i64) -> String {
let mut out = String::new();
if neg {
out.push('-');
}
if exp >= 0 {
let ip_len = (exp + 1) as usize;
if ds.len() <= ip_len {
out.push_str(ds);
for _ in 0..ip_len - ds.len() {
out.push('0');
}
} else {
out.push_str(&ds[..ip_len]);
out.push('.');
out.push_str(&ds[ip_len..]);
}
} else {
out.push_str("0.");
for _ in 0..(-exp - 1) {
out.push('0');
}
out.push_str(ds);
}
out
}
fn strip_pow2(sig: &Nat, exp: i64) -> (alloc::borrow::Cow<'_, Nat>, i64) {
let tz = sig.trailing_zeros();
if tz == 0 {
(alloc::borrow::Cow::Borrowed(sig), exp)
} else {
(alloc::borrow::Cow::Owned(sig.shr(tz)), exp + tz as i64)
}
}
#[inline]
fn sign_of(neg: bool) -> Sign {
if neg { Sign::Negative } else { Sign::Positive }
}
impl Repr {
#[inline]
fn sign_bit(&self) -> bool {
match self {
Repr::NaN => false,
Repr::Inf(neg) | Repr::Zero(neg) | Repr::Normal { neg, .. } => *neg,
}
}
}
fn exp2(e: i64) -> f64 {
let mut base = if e < 0 { 0.5 } else { 2.0 };
let mut n = e.unsigned_abs();
let mut acc = 1.0f64;
while n > 0 {
if n & 1 == 1 {
acc *= base;
}
base *= base;
n >>= 1;
}
acc
}
impl Float {
fn cmp_finite(&self, other: &Float) -> Ordering {
let rank = |f: &Float| -> i8 {
match &f.repr {
Repr::Inf(true) => -2,
Repr::Normal { neg: true, .. } => -1,
Repr::Zero(_) => 0,
Repr::Normal { neg: false, .. } => 1,
Repr::Inf(false) => 2,
Repr::NaN => unreachable!(),
}
};
match rank(self).cmp(&rank(other)) {
Ordering::Equal => {}
non_eq => return non_eq,
}
if let (
Repr::Normal {
neg,
sig: sa,
exp: ea,
},
Repr::Normal {
sig: sb, exp: eb, ..
},
) = (&self.repr, &other.repr)
{
let emin = (*ea).min(*eb);
let a = sa.shl((*ea - emin) as u64);
let b = sb.shl((*eb - emin) as u64);
let m = a.cmp(&b);
return if *neg { m.reverse() } else { m };
}
Ordering::Equal
}
}
impl PartialEq for Float {
fn eq(&self, other: &Self) -> bool {
self.partial_cmp(other) == Some(Ordering::Equal)
}
}
impl PartialOrd for Float {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.is_nan() || other.is_nan() {
return None;
}
Some(self.cmp_finite(other))
}
}
macro_rules! float_binop {
($tr:ident, $method:ident, $inherent:ident) => {
impl core::ops::$tr for Float {
type Output = Float;
#[inline]
fn $method(self, rhs: Float) -> Float {
let p = self.precision().max(rhs.precision());
Float::$inherent(&self, &rhs, p, RoundingMode::Nearest)
}
}
impl core::ops::$tr<&Float> for &Float {
type Output = Float;
#[inline]
fn $method(self, rhs: &Float) -> Float {
let p = self.precision().max(rhs.precision());
Float::$inherent(self, rhs, p, RoundingMode::Nearest)
}
}
};
}
float_binop!(Add, add, add);
float_binop!(Sub, sub, sub);
float_binop!(Mul, mul, mul);
float_binop!(Div, div, div);
impl core::ops::Neg for Float {
type Output = Float;
#[inline]
fn neg(self) -> Float {
Float::neg(&self)
}
}
impl core::ops::Neg for &Float {
type Output = Float;
#[inline]
fn neg(self) -> Float {
Float::neg(self)
}
}
impl FromStr for Float {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s.trim() {
t if t.eq_ignore_ascii_case("nan") => Ok(Float::nan(53)),
t if t.eq_ignore_ascii_case("inf") || t.eq_ignore_ascii_case("+inf") => {
Ok(Float::infinity(53))
}
t if t.eq_ignore_ascii_case("-inf") => Ok(Float::neg_infinity(53)),
t => {
let r: Rational = t.parse()?;
Ok(Float::from_rational(&r, 53, RoundingMode::Nearest))
}
}
}
}
fn plain_to_scientific(s: &str, upper: bool) -> String {
if matches!(s, "NaN" | "inf" | "-inf") {
return String::from(s);
}
let (neg, body) = match s.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, s),
};
let e_char = if upper { 'E' } else { 'e' };
let (int_part, frac_part) = match body.split_once('.') {
Some((i, f)) => (i, f),
None => (body, ""),
};
let combined: String = int_part.chars().chain(frac_part.chars()).collect();
let point = int_part.len();
let mut out = String::new();
if neg {
out.push('-');
}
match combined.find(|c| c != '0') {
None => {
out.push('0');
out.push(e_char);
out.push('0');
}
Some(p) => {
let exp = point as i64 - 1 - p as i64;
let mut sig = &combined[p..];
sig = sig.trim_end_matches('0');
let bytes = sig.as_bytes();
out.push(bytes[0] as char);
if bytes.len() > 1 {
out.push('.');
out.push_str(&sig[1..]);
}
out.push(e_char);
out.push_str(&alloc::format!("{exp}"));
}
}
out
}
impl fmt::Display for Float {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match f.precision() {
Some(p) => self.to_decimal_string(p as u32),
None => self.to_shortest_string(),
};
f.write_str(&s)
}
}
impl fmt::LowerExp for Float {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&plain_to_scientific(&self.to_shortest_string(), false))
}
}
impl fmt::UpperExp for Float {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&plain_to_scientific(&self.to_shortest_string(), true))
}
}
impl fmt::Debug for Float {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Float({self} @ {}bit)", self.precision)
}
}
const NEAR: RoundingMode = RoundingMode::Nearest;
const LN_AGM_THRESHOLD: u64 = 1 << 12;
fn iflt(k: i64, w: u64) -> Float {
Float::from_int(&Int::from_i64(k), w, NEAR)
}
fn rflt(num: i64, den: i64, w: u64) -> Float {
Float::from_rational(
&Rational::new(Int::from_i64(num), Int::from_i64(den)),
w,
NEAR,
)
}
impl Float {
fn scale_pow2(&self, k: i64) -> Float {
match &self.repr {
Repr::Normal { neg, sig, exp } => Float {
repr: Repr::Normal {
neg: *neg,
sig: sig.clone(),
exp: exp + k,
},
precision: self.precision,
},
_ => self.clone(),
}
}
fn round_half_up_to_int(&self) -> Int {
let w = self.precision + 2;
let shifted = self.add(&rflt(1, 2, w), w, NEAR);
shifted
.to_rational()
.map(|r| r.floor())
.unwrap_or(Int::ZERO)
}
fn ziv<F: Fn(u64) -> Float>(prec: u64, mode: RoundingMode, f: F) -> Float {
let prec = prec.max(1);
let mut guard = 48u64;
loop {
let val = f(prec + guard);
if let Some(r) = round_ziv(&val, prec, mode) {
return r;
}
if guard > prec + 4096 {
return val.round(prec, mode); }
guard = guard.saturating_mul(2);
}
}
pub fn pi(precision: u64, mode: RoundingMode) -> Float {
Float::ziv(precision, mode, pi_at)
}
pub fn ln2(precision: u64, mode: RoundingMode) -> Float {
Float::ziv(precision, mode, ln2_at)
}
pub fn e(precision: u64, mode: RoundingMode) -> Float {
Float::ziv(precision, mode, |w| exp_at(&iflt(1, w), w))
}
pub fn euler_gamma(precision: u64, mode: RoundingMode) -> Float {
Float::ziv(precision, mode, gamma_at)
}
pub fn catalan(precision: u64, mode: RoundingMode) -> Float {
Float::ziv(precision, mode, catalan_at)
}
pub fn exp(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(true) => Float::zero(precision),
Repr::Inf(false) => Float::infinity(precision),
Repr::Zero(_) => Float::from_int(&Int::ONE, precision, mode),
Repr::Normal { .. } => match crate::float_mp::exp_mp(self, precision, mode) {
Some(v) => v,
None => {
let x = self.clone();
Float::ziv(precision, mode, move |w| exp_at(&x.round(w, NEAR), w))
}
},
}
}
pub fn ln(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(false) => Float::infinity(precision),
Repr::Inf(true) => Float::nan(precision),
Repr::Zero(_) => Float::neg_infinity(precision),
Repr::Normal { neg: true, .. } => Float::nan(precision),
Repr::Normal { .. } => {
let x = self.clone();
Float::ziv(precision, mode, move |w| ln_at(&x.round(w, NEAR), w))
}
}
}
pub fn sin(&self, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
if self.is_zero() {
return Float::zero_signed(self.is_sign_negative(), precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| sin_cos_at(&x.round(w, NEAR), w).0)
}
pub fn cos(&self, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
if self.is_zero() {
return Float::from_int(&Int::ONE, precision, mode);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| sin_cos_at(&x.round(w, NEAR), w).1)
}
pub fn tan(&self, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
if self.is_zero() {
return Float::zero_signed(self.is_sign_negative(), precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let (s, c) = sin_cos_at(&x.round(w, NEAR), w);
s.div(&c, w, NEAR)
})
}
pub fn atan(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(neg) => {
let half_pi = Float::pi(precision + 8, NEAR).scale_pow2(-1);
if *neg { half_pi.neg() } else { half_pi }.round(precision, mode)
}
Repr::Zero(_) => Float::zero_signed(self.is_sign_negative(), precision),
Repr::Normal { .. } => {
let x = self.clone();
Float::ziv(precision, mode, move |w| atan_at(&x.round(w, NEAR), w))
}
}
}
pub fn sinh(&self, precision: u64, mode: RoundingMode) -> Float {
if self.is_nan() {
return Float::nan(precision);
}
if self.is_infinite() {
return self.clone().round(precision, mode);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let ex = x.exp(w, NEAR);
let emx = x.neg().exp(w, NEAR);
ex.sub(&emx, w, NEAR).scale_pow2(-1)
})
}
pub fn cosh(&self, precision: u64, mode: RoundingMode) -> Float {
if self.is_nan() {
return Float::nan(precision);
}
if self.is_infinite() {
return Float::infinity(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let ex = x.exp(w, NEAR);
let emx = x.neg().exp(w, NEAR);
ex.add(&emx, w, NEAR).scale_pow2(-1)
})
}
pub fn tanh(&self, precision: u64, mode: RoundingMode) -> Float {
if self.is_nan() {
return Float::nan(precision);
}
if self.is_infinite() {
return Float::from_int(&Int::from_i64(self.signum_i()), precision, mode);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
x.sinh(w, NEAR).div(&x.cosh(w, NEAR), w, NEAR)
})
}
pub fn asin(&self, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let one = Float::from_int(&Int::ONE, w, NEAR);
let xr = x.round(w, NEAR);
let denom = one.sub(&xr.mul(&xr, w, NEAR), w, NEAR).sqrt(w, NEAR);
xr.div(&denom, w, NEAR).atan(w, NEAR)
})
}
pub fn acos(&self, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let half_pi = Float::pi(w, NEAR).scale_pow2(-1);
half_pi.sub(&x.asin(w, NEAR), w, NEAR)
})
}
pub fn atan2(&self, x: &Float, precision: u64, mode: RoundingMode) -> Float {
if self.is_nan() || x.is_nan() {
return Float::nan(precision);
}
let (y, x) = (self.clone(), x.clone());
Float::ziv(precision, mode, move |w| {
let pi = Float::pi(w, NEAR);
if x.is_zero() {
if y.is_zero() {
return Float::zero(w);
}
let hp = pi.scale_pow2(-1);
return if y.is_sign_negative() { hp.neg() } else { hp };
}
let base = y.div(&x, w, NEAR).atan(w, NEAR);
if !x.is_sign_negative() {
base
} else if !y.is_sign_negative() {
base.add(&pi, w, NEAR)
} else {
base.sub(&pi, w, NEAR)
}
})
}
pub fn pow(&self, y: &Float, precision: u64, mode: RoundingMode) -> Float {
if self.is_nan() || y.is_nan() {
return Float::nan(precision);
}
if y.is_zero() {
return Float::from_int(&Int::ONE, precision, mode);
}
if self.is_zero() {
return if y.is_sign_negative() {
Float::infinity(precision)
} else {
Float::zero(precision)
};
}
if self.is_sign_negative() {
return Float::nan(precision);
}
let (base, y) = (self.clone(), y.clone());
Float::ziv(precision, mode, move |w| {
y.mul(&base.ln(w, NEAR), w, NEAR).exp(w, NEAR)
})
}
pub fn asinh(&self, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return self.clone().round(precision, mode);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let one = Float::from_int(&Int::ONE, w, NEAR);
let xr = x.round(w, NEAR);
let root = xr.mul(&xr, w, NEAR).add(&one, w, NEAR).sqrt(w, NEAR);
xr.add(&root, w, NEAR).ln(w, NEAR)
})
}
pub fn acosh(&self, precision: u64, mode: RoundingMode) -> Float {
if self.is_nan() {
return Float::nan(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let one = Float::from_int(&Int::ONE, w, NEAR);
let xr = x.round(w, NEAR);
let root = xr.mul(&xr, w, NEAR).sub(&one, w, NEAR).sqrt(w, NEAR);
xr.add(&root, w, NEAR).ln(w, NEAR)
})
}
pub fn atanh(&self, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
let one = Float::from_int(&Int::ONE, w, NEAR);
let xr = x.round(w, NEAR);
let ratio = one.add(&xr, w, NEAR).div(&one.sub(&xr, w, NEAR), w, NEAR);
ratio.ln(w, NEAR).scale_pow2(-1)
})
}
pub fn erf(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(neg) => Float::from_int(
if *neg { &Int::MINUS_ONE } else { &Int::ONE },
precision,
mode,
),
Repr::Zero(_) => Float::zero_signed(self.is_sign_negative(), precision),
Repr::Normal { .. } => {
let x = self.clone();
Float::ziv(precision, mode, move |w| erf_at(&x.round(w, NEAR), w))
}
}
}
pub fn erfc(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(false) => Float::zero(precision),
Repr::Inf(true) => Float::from_int(&Int::from_i64(2), precision, mode),
Repr::Zero(_) => Float::from_int(&Int::ONE, precision, mode),
Repr::Normal { .. } => {
let x = self.clone();
Float::ziv(precision, mode, move |w| erfc_at(&x.round(w, NEAR), w))
}
}
}
pub fn zeta(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(false) => Float::from_int(&Int::ONE, precision, mode),
Repr::Inf(true) => Float::nan(precision),
Repr::Zero(_) => Float::from_rational(
&Rational::new(Int::MINUS_ONE, Int::from_i64(2)),
precision,
mode,
),
Repr::Normal { neg: true, .. } => Float::nan(precision),
Repr::Normal { .. } => {
if self.partial_cmp(&Float::from_int(&Int::ONE, self.precision, NEAR))
== Some(Ordering::Equal)
{
return Float::infinity(precision);
}
let s = self.clone();
Float::ziv(precision, mode, move |w| zeta_at(&s.round(w, NEAR), w))
}
}
}
pub fn gamma(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(false) => Float::infinity(precision),
Repr::Inf(true) => Float::nan(precision),
Repr::Zero(_) => Float::nan(precision),
Repr::Normal { .. } => {
if let Some(r) = self.to_rational()
&& r.denominator() == &Int::ONE
&& r.numerator().is_negative()
{
return Float::nan(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| gamma_fn_at(&x.round(w, NEAR), w))
}
}
}
pub fn ln_gamma(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(false) => Float::infinity(precision),
Repr::Inf(true) => Float::nan(precision),
Repr::Zero(_) => Float::infinity(precision),
Repr::Normal { neg: true, .. } => Float::nan(precision),
Repr::Normal { .. } => {
let x = self.clone();
Float::ziv(precision, mode, move |w| ln_gamma_at(&x.round(w, NEAR), w))
}
}
}
pub fn bessel_j(&self, n: i64, precision: u64, mode: RoundingMode) -> Float {
self.bessel(n, precision, mode, true)
}
pub fn bessel_i(&self, n: i64, precision: u64, mode: RoundingMode) -> Float {
self.bessel(n, precision, mode, false)
}
fn bessel(&self, n: i64, precision: u64, mode: RoundingMode, alternating: bool) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
let order = n.unsigned_abs();
let flip = alternating && n < 0 && order % 2 == 1;
if self.is_zero() {
return if order == 0 {
Float::from_int(&Int::ONE, precision, mode)
} else {
Float::zero(precision)
};
}
let x = self.clone();
let res = Float::ziv(precision, mode, move |w| {
bessel_series_at(order, &x.round(w, NEAR), w, alternating)
});
if flip { res.neg() } else { res }
}
pub fn digamma(&self, precision: u64, mode: RoundingMode) -> Float {
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(false) => Float::infinity(precision),
Repr::Inf(true) => Float::nan(precision),
Repr::Zero(_) => Float::nan(precision),
Repr::Normal { .. } => {
if is_nonpos_int(self) {
return Float::nan(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| digamma_at(&x.round(w, NEAR), w))
}
}
}
pub fn polygamma(&self, n: u64, precision: u64, mode: RoundingMode) -> Float {
if n == 0 {
return self.digamma(precision, mode);
}
match &self.repr {
Repr::NaN => Float::nan(precision),
Repr::Inf(false) => Float::zero(precision),
Repr::Inf(true) => Float::nan(precision),
Repr::Zero(_) => Float::nan(precision),
Repr::Normal { .. } => {
if is_nonpos_int(self) {
return Float::nan(precision);
}
let x = self.clone();
Float::ziv(precision, mode, move |w| {
polygamma_at(n, &x.round(w, NEAR), w)
})
}
}
}
pub fn beta(a: &Float, b: &Float, precision: u64, mode: RoundingMode) -> Float {
if !a.is_finite() || !b.is_finite() {
return Float::nan(precision);
}
if is_nonpos_int(a) || is_nonpos_int(b) {
return Float::nan(precision);
}
if let (Some(ra), Some(rb)) = (a.to_rational(), b.to_rational()) {
let sum = ra.add(&rb);
if sum.denominator() == &Int::ONE && !sum.numerator().is_positive() {
return Float::zero(precision);
}
}
let a = a.clone();
let b = b.clone();
Float::ziv(precision, mode, move |w| {
beta_at(&a.round(w, NEAR), &b.round(w, NEAR), w)
})
}
pub fn bessel_y(&self, n: i64, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
if self.is_zero() {
return Float::neg_infinity(precision);
}
if self.is_sign_negative() {
return Float::nan(precision);
}
let order = n.unsigned_abs();
let flip = n < 0 && order % 2 == 1;
let x = self.clone();
let res = Float::ziv(precision, mode, move |w| {
bessel_y_at(order, &x.round(w, NEAR), w)
});
if flip { res.neg() } else { res }
}
pub fn bessel_k(&self, n: i64, precision: u64, mode: RoundingMode) -> Float {
if !self.is_finite() {
return Float::nan(precision);
}
if self.is_zero() {
return Float::infinity(precision);
}
if self.is_sign_negative() {
return Float::nan(precision);
}
let order = n.unsigned_abs();
let x = self.clone();
Float::ziv(precision, mode, move |w| {
bessel_k_at(order, &x.round(w, NEAR), w)
})
}
fn signum_i(&self) -> i64 {
match self.sign() {
Sign::Negative => -1,
Sign::Zero => 0,
Sign::Positive => 1,
}
}
}
fn round_ziv(val: &Float, prec: u64, mode: RoundingMode) -> Option<Float> {
match &val.repr {
Repr::Normal { sig, .. } => {
let w = sig.bit_len();
if w <= prec {
return Some(val.round(prec, mode));
}
let drop = w - prec;
const CHECK: u64 = 24;
if drop <= CHECK + 1 {
return None;
}
let low = sig.low_bits(drop);
let margin = Nat::one().shl(drop - CHECK);
let full = Nat::one().shl(drop);
let ambiguous = if mode == RoundingMode::Nearest {
let half = Nat::one().shl(drop - 1);
let dist = if low >= half {
low.checked_sub(&half).unwrap()
} else {
half.checked_sub(&low).unwrap()
};
dist < margin
} else {
low < margin || full.checked_sub(&low).unwrap() < margin
};
if ambiguous {
None
} else {
Some(val.round(prec, mode))
}
}
_ => Some(val.round(prec, mode)),
}
}
fn odd_series_scale(x: &Float, w: u64) -> Option<u64> {
let e = match &x.repr {
Repr::Normal { sig, exp, .. } => exp + sig.bit_len() as i64 - 1, _ => return None, };
if e <= -(w as i64) / 2 - 28 {
return None;
}
Some(w + 32 + (-e).max(0) as u64)
}
const ODD_SERIES_RECT_THRESHOLD: u64 = 1536;
fn atanh_series(x: &Float, w: u64) -> Float {
if w >= ODD_SERIES_RECT_THRESHOLD {
odd_series_rect(x, w, false)
} else {
atanh_series_simple(x, w)
}
}
fn atanh_series_simple(x: &Float, w: u64) -> Float {
debug_assert!(!x.is_sign_negative(), "atanh series needs x >= 0");
let Some(n) = odd_series_scale(x, w) else {
return x.round(w, NEAR);
};
let xs = scaled_int(x, n as i64).magnitude();
let x2 = xs.square().shr(n);
let mut pow = xs.clone();
let mut sum = xs;
let mut k = 1u64;
loop {
pow = pow.mul(&x2).shr(n);
let term = pow.div_rem(&Nat::from_u64(2 * k + 1)).expect("odd > 0").0;
if term.is_zero() {
break;
}
sum = sum.add(&term);
k += 1;
}
Float::round_raw(false, sum, -(n as i64), w, NEAR).0
}
fn atan_series(x: &Float, w: u64) -> Float {
if w >= ODD_SERIES_RECT_THRESHOLD {
odd_series_rect(x, w, true)
} else {
atan_series_simple(x, w)
}
}
fn atan_series_simple(x: &Float, w: u64) -> Float {
debug_assert!(!x.is_sign_negative(), "atan series needs x >= 0");
let Some(n) = odd_series_scale(x, w) else {
return x.round(w, NEAR);
};
let xs = scaled_int(x, n as i64).magnitude();
let x2 = xs.square().shr(n);
let mut pow = xs.clone();
let mut sum = xs;
let mut k = 1u64;
let mut sub = true;
loop {
pow = pow.mul(&x2).shr(n);
let term = pow.div_rem(&Nat::from_u64(2 * k + 1)).expect("odd > 0").0;
if term.is_zero() {
break;
}
sum = if sub {
sum.checked_sub(&term)
.expect("alternating partial sums stay non-negative")
} else {
sum.add(&term)
};
sub = !sub;
k += 1;
}
Float::round_raw(false, sum, -(n as i64), w, NEAR).0
}
fn odd_series_rect(x: &Float, w: u64, alternating: bool) -> Float {
debug_assert!(!x.is_sign_negative(), "odd series needs x >= 0");
let Some(n) = odd_series_scale(x, w) else {
return x.round(w, NEAR);
};
let xs = scaled_int(x, n as i64).magnitude();
let z = xs.square().shr(n); let t = series_term_count(&z, n, |k| 2 * k - 1, |k| 2 * k + 1);
let c = rect_block_width(t);
let powers = scaled_powers(&z, n, c);
let bracket = power_series_rect(
n,
c,
&powers,
alternating,
true,
|k| 2 * k - 1,
|k| 2 * k + 1,
);
let sum = xs.mul(&bracket).shr(n);
Float::round_raw(false, sum, -(n as i64), w, NEAR).0
}
fn atan_recip_scaled(q: u64, n: u64) -> Nat {
let q2 = q * q;
let l = 63 - q2.leading_zeros() as u64;
let k = n / l + 2;
let (num, o, p) = split_atan_sum(0, k, q2, true);
debug_assert!(num.is_positive(), "atan series sum must stay positive");
num.magnitude()
.mul(&Nat::from_u64(q))
.shl(n)
.div_rem(&p.mul(&o))
.expect("denominator > 0")
.0
}
fn from_const(sig: &[u64], w: u64) -> Float {
let keep = (((w + 16) / 64 + 2) as usize).min(sig.len());
let drop = sig.len() - keep;
let mag = Nat::from_limbs(&sig[drop..]);
let exp = drop as i64 * 64 - crate::float_consts::CONST_BITS as i64; Float::round_raw(false, mag, exp, w, NEAR).0
}
pub(crate) fn ln2_embedded(w: u64) -> Option<Float> {
(w + 16 <= crate::float_consts::CONST_BITS)
.then(|| from_const(&crate::float_consts::LN2_SIG, w))
}
pub(crate) fn round_ratio(num: &Int, den: &Int, exp2: i64, w: u64, mode: RoundingMode) -> Float {
let g = w as i64 + 32; let q = num.mul_2k(g as u32).div_floor(den).magnitude();
Float::round_raw(false, q, exp2 - g, w, mode).0
}
pub(crate) fn round_const_bits(sig: &[u64], total_bits: u64, w: u64) -> Float {
let keep = (((w + 48) / 64 + 2) as usize).min(sig.len());
let drop = sig.len() - keep;
let mag = Nat::from_limbs(&sig[drop..]);
Float::round_raw(false, mag, drop as i64 * 64 - total_bits as i64, w, NEAR).0
}
fn gamma_at(w: u64) -> Float {
let n = w + 32;
let bign = (n as i64) * 185 / 1024 + 8; let scale = Int::ONE.mul_2k(n as u32); let n2 = Int::from_i64(bign * bign);
let mut t = scale.clone(); let mut b = t.clone(); let mut hs = Int::ZERO; let mut a = Int::ZERO; let mut k = 1i64;
loop {
t = t.mul(&n2).div_trunc(&Int::from_i64(k * k));
if t.is_zero() {
break;
}
hs = hs.add(&scale.div_trunc(&Int::from_i64(k)));
b = b.add(&t);
a = a.add(&t.mul(&hs).div_2k_trunc(n as u32));
k += 1;
}
let af = Float::round_raw(false, a.magnitude(), -(n as i64), n, NEAR).0;
let bf = Float::round_raw(false, b.magnitude(), -(n as i64), n, NEAR).0;
let lnn = Float::from_int(&Int::from_i64(bign), n, NEAR).ln(n, NEAR);
af.div(&bf, n, NEAR).sub(&lnn, w, NEAR)
}
fn catalan_at(w: u64) -> Float {
let n = w + 32;
let mut term = Int::ONE.mul_2k(n as u32); let mut sum = term.clone();
let mut k = 1i64;
loop {
let num = Int::from_i64((2 * k - 1) * k);
let den = Int::from_i64(2 * (2 * k + 1) * (2 * k + 1));
term = term.mul(&num).div_trunc(&den);
if term.is_zero() {
break;
}
sum = sum.add(&term);
k += 1;
}
let s = Float::round_raw(false, sum.magnitude(), -(n as i64), n, NEAR).0;
let sqrt3 = Float::from_int(&Int::from_i64(3), n, NEAR).sqrt(n, NEAR);
let ln_term = Float::from_int(&Int::from_i64(2), n, NEAR)
.add(&sqrt3, n, NEAR)
.ln(n, NEAR);
let eight = Float::from_int(&Int::from_i64(8), n, NEAR);
let term1 = pi_at(n).mul(&ln_term, n, NEAR).div(&eight, n, NEAR);
let term2 = s
.mul(&Float::from_int(&Int::from_i64(3), n, NEAR), n, NEAR)
.div(&eight, n, NEAR);
term1.add(&term2, w, NEAR)
}
fn pi_at(w: u64) -> Float {
if w + 16 <= crate::float_consts::CONST_BITS {
return from_const(&crate::float_consts::PI_SIG, w);
}
let n = w + 32; let a1 = atan_recip_scaled(5, n);
let a2 = atan_recip_scaled(239, n);
let pi_scaled = a1
.shl(4)
.checked_sub(&a2.shl(2))
.expect("16·atan(1/5) > 4·atan(1/239)");
Float::round_raw(false, pi_scaled, -(n as i64), w, NEAR).0
}
fn floor_log2(x: &Float) -> i64 {
match &x.repr {
Repr::Normal { sig, exp, .. } => exp + sig.bit_len() as i64 - 1,
_ => i64::MIN,
}
}
fn ilog2(w: u64) -> u64 {
63 - w.max(1).leading_zeros() as u64
}
fn agm(a: &Float, b: &Float, w: u64) -> Float {
let mut a = a.round(w, NEAR);
let mut b = b.round(w, NEAR);
let max_iters = 2 * ilog2(w) + 16;
for _ in 0..max_iters {
let a1 = a.add(&b, w, NEAR).scale_pow2(-1);
let b1 = a.mul(&b, w, NEAR).sqrt(w, NEAR);
let d = a1.sub(&b1, w, NEAR);
a = a1;
b = b1;
if d.is_zero() || floor_log2(&d) <= floor_log2(&a) - w as i64 {
return a;
}
}
a.add(&b, w, NEAR).scale_pow2(-1)
}
fn ln_near_one(x: &Float) -> bool {
let d = x.sub(&iflt(1, 64), 64, NEAR);
d.is_zero() || floor_log2(&d) < -32
}
fn ln_agm_at(x: &Float, w: u64) -> Float {
let n = w + 64 + 4 * ilog2(w);
let e = floor_log2(x);
let target = n as i64 / 2 + 16 + ilog2(n) as i64;
let m = target - e;
let s = x.scale_pow2(m); let four_over_s = iflt(4, n).div(&s, n, NEAR);
let agm = agm(&iflt(1, n), &four_over_s, n);
let ln_s = pi_at(n).div(&agm.scale_pow2(1), n, NEAR); let m_ln2 = iflt(m, n).mul(&ln2_at(n), n, NEAR);
ln_s.sub(&m_ln2, n, NEAR).round(w, NEAR)
}
fn split_atan_sum(a: u64, b: u64, m: u64, alternating: bool) -> (Int, Nat, Nat) {
if b - a <= 4 && m < (1 << 16) && 2 * b + 1 < (1 << 24) {
let mut n: i128 = 0;
let mut o: u128 = 1;
let mut p: u128 = 1;
for k in a..b {
let odd = 2 * k + 1;
let t = if alternating && k & 1 == 1 {
-(o as i128)
} else {
o as i128
};
n = n * m as i128 * odd as i128 + t;
o *= odd as u128;
p *= m as u128;
}
return (Int::from_i128(n), Nat::from_u128(o), Nat::from_u128(p));
}
if b - a == 1 {
let n = if alternating && a & 1 == 1 {
Int::MINUS_ONE
} else {
Int::ONE
};
return (n, Nat::from_u64(2 * a + 1), Nat::from_u64(m));
}
let c = a + (b - a) / 2;
let (n1, o1, p1) = split_atan_sum(a, c, m, alternating);
let (n2, o2, p2) = split_atan_sum(c, b, m, alternating);
let n = n1
.mul(&Int::from(p2.mul(&o2)))
.add(&n2.mul(&Int::from(o1.clone())));
(n, o1.mul(&o2), p1.mul(&p2))
}
fn ln2_at(w: u64) -> Float {
if w + 16 <= crate::float_consts::CONST_BITS {
return from_const(&crate::float_consts::LN2_SIG, w);
}
let n = w + 32;
let k = n / 3 + 2;
let (num, o, p) = split_atan_sum(0, k, 9, false);
let sum = num
.magnitude()
.mul(&Nat::from_u64(6))
.shl(n)
.div_rem(&p.mul(&o))
.expect("denominator > 0")
.0;
Float::round_raw(false, sum, -(n as i64), w, NEAR).0
}
const EXP_RECT_THRESHOLD: u64 = 1536;
fn exp_taylor_sum_simple(rs: &Int, n: u64) -> Int {
let mut sum = Int::ONE.mul_2k(n as u32);
let mut term = sum.clone();
let mut kk: i64 = 1;
loop {
term = term
.mul(rs)
.div_2k_trunc(n as u32)
.div_trunc(&Int::from_i64(kk));
if term.is_zero() {
break;
}
sum = sum.add(&term);
kk += 1;
}
sum
}
fn exp_taylor_sum_rect(rs: &Int, n: u64) -> Int {
let zmag = rs.magnitude();
if zmag.is_zero() {
return Int::ONE.mul_2k(n as u32); }
let t = series_term_count(&zmag, n, |_| 1, |k| k);
let c = rect_block_width(t);
let powers = scaled_powers(&zmag, n, c);
let sum = power_series_rect(n, c, &powers, rs.is_negative(), false, |_| 1, |k| k);
Int::from(sum)
}
fn exp_at(x: &Float, w: u64) -> Float {
let j = w.isqrt().max(1);
let n = w + j + 8;
let ln2 = ln2_at(n);
let k = x.div(&ln2, n, NEAR).round_half_up_to_int();
let ki = k.to_i64().unwrap_or(0);
let r = x.sub(&Float::from_int(&k, n, NEAR).mul(&ln2, n, NEAR), n, NEAR);
let r = r.scale_pow2(-(j as i64)); let rs = scaled_int(&r, n as i64);
let mut sum = if w >= EXP_RECT_THRESHOLD {
exp_taylor_sum_rect(&rs, n)
} else {
exp_taylor_sum_simple(&rs, n)
};
for _ in 0..j {
sum = sum.square().div_2k_trunc(n as u32);
}
debug_assert!(sum.is_positive(), "exp series sum must stay positive");
Float::round_raw(false, sum.magnitude(), -(n as i64), n, NEAR)
.0
.scale_pow2(ki)
}
fn scaled_int(x: &Float, n: i64) -> Int {
match &x.repr {
Repr::Normal { neg, sig, exp } => {
let e = exp + n;
let mag = if e >= 0 {
sig.shl(e as u64)
} else {
sig.shr((-e) as u64)
};
let v = Int::from(mag);
if *neg { v.neg() } else { v }
}
_ => Int::ZERO,
}
}
fn ln_at(x: &Float, w: u64) -> Float {
if x.is_zero() {
return Float::neg_infinity(w);
}
if w >= LN_AGM_THRESHOLD && !ln_near_one(x) {
return ln_agm_at(x, w);
}
ln_series_at(x, w)
}
fn ln_series_at(x: &Float, w: u64) -> Float {
let bits = x.significand().map(|s| s.bit_len() as i64).unwrap_or(0);
let e = x.exponent().unwrap_or(0) + bits - 1; let m = x.scale_pow2(-e); let one = iflt(1, w);
let y = m.sub(&one, w, NEAR).div(&m.add(&one, w, NEAR), w, NEAR);
let ln_m = iflt(2, w).mul(&atanh_series(&y, w), w, NEAR);
iflt(e, w).mul(&ln2_at(w), w, NEAR).add(&ln_m, w, NEAR)
}
fn sin_cos_at(x: &Float, w: u64) -> (Float, Float) {
let pi = pi_at(w);
let half_pi = pi.scale_pow2(-1);
let q = x.div(&half_pi, w, NEAR).round_half_up_to_int();
let r = x.sub(
&Float::from_int(&q, w, NEAR).mul(&half_pi, w, NEAR),
w,
NEAR,
);
let (sr, cr) = sin_cos_series(&r, w);
let quad = q.rem_euclid(&Int::from_i64(4)).to_i64().unwrap_or(0);
match quad {
0 => (sr, cr),
1 => (cr, sr.neg()),
2 => (sr.neg(), cr.neg()),
_ => (cr.neg(), sr),
}
}
const SIN_COS_RECT_THRESHOLD: u64 = 1024;
fn sin_cos_series(r: &Float, w: u64) -> (Float, Float) {
if w >= SIN_COS_RECT_THRESHOLD {
sin_cos_series_rect(r, w)
} else {
sin_cos_series_simple(r, w)
}
}
fn sin_cos_series_simple(r: &Float, w: u64) -> (Float, Float) {
if r.is_zero() {
return (r.round(w, NEAR), iflt(1, w));
}
let n = w + 32;
let z = scaled_int(r, n as i64).magnitude().square().shr(n);
let mut term = Nat::one().shl(n);
let mut cos = term.clone();
let mut m = 1u64;
let mut sub = true;
loop {
term = term
.mul(&z)
.shr(n)
.div_rem(&Nat::from_u64((2 * m - 1) * (2 * m)))
.expect("> 0")
.0;
if term.is_zero() {
break;
}
cos = if sub {
cos.checked_sub(&term)
.expect("alternating partial sums stay non-negative")
} else {
cos.add(&term)
};
sub = !sub;
m += 1;
}
let mut term = Nat::one().shl(n);
let mut bracket = term.clone();
let mut k = 1u64;
let mut sub = true;
loop {
term = term
.mul(&z)
.shr(n)
.div_rem(&Nat::from_u64((2 * k) * (2 * k + 1)))
.expect("> 0")
.0;
if term.is_zero() {
break;
}
bracket = if sub {
bracket
.checked_sub(&term)
.expect("alternating partial sums stay non-negative")
} else {
bracket.add(&term)
};
sub = !sub;
k += 1;
}
let bracket_f = Float::round_raw(false, bracket, -(n as i64), w, NEAR).0;
(
r.mul(&bracket_f, w, NEAR),
Float::round_raw(false, cos, -(n as i64), w, NEAR).0,
)
}
fn sin_cos_series_rect(r: &Float, w: u64) -> (Float, Float) {
if r.is_zero() {
return (r.round(w, NEAR), iflt(1, w));
}
let n = w + 32;
let z = scaled_int(r, n as i64).magnitude().square().shr(n);
if z.is_zero() {
return (r.round(w, NEAR), iflt(1, w));
}
let t_est = sin_cos_term_count(&z, n);
let two_t = 2 * t_est;
let mut c = two_t.isqrt();
if c * c < two_t {
c += 1;
}
let c = (c as usize).max(2);
let mut powers = alloc::vec::Vec::with_capacity(c + 1);
powers.push(Nat::one().shl(n)); powers.push(z.clone()); for j in 2..=c {
powers.push(powers[j - 1].mul(&z).shr(n));
}
let cos = power_series_rect(n, c, &powers, true, false, |_| 1, |m| (2 * m - 1) * (2 * m));
let bracket = power_series_rect(n, c, &powers, true, false, |_| 1, |k| (2 * k) * (2 * k + 1));
let bracket_f = Float::round_raw(false, bracket, -(n as i64), w, NEAR).0;
(
r.mul(&bracket_f, w, NEAR),
Float::round_raw(false, cos, -(n as i64), w, NEAR).0,
)
}
fn sin_cos_term_count(z: &Nat, n: u64) -> u64 {
let log2z_x2 = 2 * z.bit_len() as i64 - 1 - 2 * n as i64;
let mut lg2 = 0i64; let mut m = 0u64;
loop {
m += 1;
let dm = (2 * m - 1) * (2 * m);
let log2dm_x2 = 2 * (64 - dm.leading_zeros() as i64) - 1;
lg2 += log2z_x2 - log2dm_x2;
if lg2 < -2 * n as i64 || m >= n {
return m;
}
}
}
fn power_series_rect(
n: u64,
c: usize,
powers: &[Nat],
alternating: bool,
has_numer: bool,
p: impl Fn(u64) -> u64,
q: impl Fn(u64) -> u64,
) -> Nat {
let acc = power_series_rect_signed(n, c, powers, alternating, has_numer, p, q);
debug_assert!(!acc.is_negative(), "series partial sum stays non-negative");
acc.magnitude()
}
fn power_series_rect_signed(
n: u64,
c: usize,
powers: &[Nat],
alternating: bool,
has_numer: bool,
p: impl Fn(u64) -> u64,
q: impl Fn(u64) -> u64,
) -> Int {
let y = &powers[c]; let mut acc = Int::ZERO; let mut b_mag = powers[0].clone(); let mut b_neg = false;
let mut base = 0u64; let cap = 2 * (base_cap(c) + c as u64);
while !b_mag.is_zero() && base <= cap {
let mut prep = alloc::vec::Vec::new();
if has_numer {
prep.reserve(c);
prep.push(Nat::one());
for j in 1..c {
prep.push(prep[j - 1].mul(&Nat::from_u64(p(base + j as u64))));
}
}
let mut w_acc = Int::ZERO;
let mut suf = Nat::one();
for j in (0..c).rev() {
let coef = if has_numer {
prep[j].mul(&suf)
} else {
suf.clone()
};
let contrib = Int::from(coef.mul(&powers[j]));
w_acc = if alternating && j % 2 == 1 {
w_acc.sub(&contrib)
} else {
w_acc.add(&contrib)
};
if j > 0 {
suf = suf.mul(&Nat::from_u64(q(base + j as u64)));
}
}
let den = suf; let block_mag = b_mag
.mul(&w_acc.magnitude())
.shr(n)
.div_rem(&den)
.expect("den > 0")
.0;
let block_neg = b_neg ^ w_acc.is_negative();
let block = Int::from(block_mag);
acc = if block_neg {
acc.sub(&block)
} else {
acc.add(&block)
};
let den_adv = den.mul(&Nat::from_u64(q(base + c as u64)));
let mut num = b_mag.mul(y);
if has_numer {
let num_adv = prep[c - 1].mul(&Nat::from_u64(p(base + c as u64)));
num = num.mul(&num_adv);
}
b_mag = num.shr(n).div_rem(&den_adv).expect("> 0").0;
if alternating && c % 2 == 1 {
b_neg = !b_neg;
}
base += c as u64;
}
acc
}
fn base_cap(c: usize) -> u64 {
4 * c as u64 * c as u64 + 64
}
fn scaled_powers(z: &Nat, n: u64, c: usize) -> alloc::vec::Vec<Nat> {
let mut powers = alloc::vec::Vec::with_capacity(c + 1);
powers.push(Nat::one().shl(n)); if c >= 1 {
powers.push(z.clone()); }
for j in 2..=c {
powers.push(powers[j - 1].mul(z).shr(n));
}
powers
}
fn rect_block_width(t: u64) -> usize {
let two_t = 2 * t.max(1);
let mut c = two_t.isqrt();
if c * c < two_t {
c += 1;
}
(c as usize).max(2)
}
fn series_term_count(z: &Nat, n: u64, p: impl Fn(u64) -> u64, q: impl Fn(u64) -> u64) -> u64 {
let log2z = z.bit_len() as i64 - 1 - n as i64;
let mut lg = 0i64; let mut k = 0u64;
loop {
k += 1;
let pk = p(k).max(1);
let qk = q(k).max(1);
let log2p = 63 - pk.leading_zeros() as i64;
let log2q = 63 - qk.leading_zeros() as i64;
lg += log2z + log2p - log2q;
if lg < -(n as i64) || k >= n.max(1) {
return k;
}
}
}
fn atan_at(x: &Float, w: u64) -> Float {
let one = iflt(1, w);
let neg = x.is_sign_negative();
let ax = x.abs();
let complement = ax.partial_cmp(&one) == Some(Ordering::Greater);
let mut arg = if complement {
one.div(&ax, w, NEAR)
} else {
ax
};
let quarter = rflt(1, 4, w);
let mut halvings = 0u32;
while arg.partial_cmp(&quarter) == Some(Ordering::Greater) {
let root = one.add(&arg.mul(&arg, w, NEAR), w, NEAR).sqrt(w, NEAR);
arg = arg.div(&one.add(&root, w, NEAR), w, NEAR);
halvings += 1;
}
let mut result = atan_series(&arg, w);
for _ in 0..halvings {
result = result.scale_pow2(1); }
if complement {
result = pi_at(w).scale_pow2(-1).sub(&result, w, NEAR);
}
if neg { result.neg() } else { result }
}
const ERF_SERIES_MAX_X2: i64 = 25;
const ERFC_SERIES_MAX_X2: i64 = 4;
fn ceil_sq_i64(x: &Float, w: u64) -> i64 {
x.mul(x, w, NEAR)
.ceil()
.and_then(|i| i.to_i64())
.unwrap_or(i64::MAX)
}
fn erf_at(x: &Float, w: u64) -> Float {
let neg = x.is_sign_negative();
let a = x.abs();
let res = if ceil_sq_i64(&a, w) <= ERF_SERIES_MAX_X2 {
erf_series(&a, w)
} else {
iflt(1, w).sub(&erfc_cf(&a, w), w, NEAR)
};
if neg { res.neg() } else { res }
}
fn erfc_at(x: &Float, w: u64) -> Float {
let neg = x.is_sign_negative();
let a = x.abs();
let core = if ceil_sq_i64(&a, w) <= ERFC_SERIES_MAX_X2 {
iflt(1, w).sub(&erf_series(&a, w), w, NEAR)
} else {
erfc_cf(&a, w)
};
if neg {
iflt(2, w).sub(&core, w, NEAR)
} else {
core
}
}
fn erf_series(a: &Float, w: u64) -> Float {
let a2c = ceil_sq_i64(a, w).max(0) as u64;
let n = w + a2c.saturating_mul(185) / 128 + 16;
let as_ = scaled_int(a, n as i64).magnitude();
let sum = if w >= ERF_RECT_THRESHOLD {
erf_kummer_sum_rect(&as_, n)
} else {
erf_kummer_sum_simple(&as_, n)
};
let s = Float::round_raw(false, sum, -(n as i64), n, NEAR).0;
let sqrtpi = pi_at(n).sqrt(n, NEAR);
let factor = iflt(2, n).div(&sqrtpi, n, NEAR);
let em = a.mul(a, n, NEAR).neg().exp(n, NEAR);
factor.mul(&em, n, NEAR).mul(&s, n, NEAR).round(w, NEAR)
}
const ERF_RECT_THRESHOLD: u64 = 1024;
fn erf_kummer_sum_simple(as_: &Nat, n: u64) -> Nat {
let two_a2 = as_.square().shr(n).shl(1); let mut term = as_.clone();
let mut sum = as_.clone();
let mut k = 1u64;
loop {
term = term
.mul(&two_a2)
.shr(n)
.div_rem(&Nat::from_u64(2 * k + 1))
.expect("odd > 0")
.0;
if term.is_zero() {
break;
}
sum = sum.add(&term);
k += 1;
}
sum
}
fn erf_kummer_sum_rect(as_: &Nat, n: u64) -> Nat {
let z = as_.square().shr(n).shl(1); if z.is_zero() {
return as_.clone(); }
let t = series_term_count(&z, n, |_| 1, |k| 2 * k + 1);
let c = rect_block_width(t);
let powers = scaled_powers(&z, n, c);
let bracket = power_series_rect(n, c, &powers, false, false, |_| 1, |k| 2 * k + 1);
as_.mul(&bracket).shr(n)
}
fn erfc_cf(a: &Float, w: u64) -> Float {
let n = w + 16;
let one = iflt(1, n);
let tiny = one.scale_pow2(-4 * (n as i64)); let x = a.round(n, NEAR);
let stop = one.scale_pow2(-(w as i64) - 4);
let mut f = tiny.clone();
let mut c = f.clone();
let mut d = Float::zero(n);
let mut j = 1u64;
let cap = 8 * w + 200;
loop {
let aj = if j == 1 {
one.clone()
} else {
rflt((j - 1) as i64, 2, n)
};
d = x.add(&aj.mul(&d, n, NEAR), n, NEAR);
if d.is_zero() {
d = tiny.clone();
}
d = one.div(&d, n, NEAR);
c = x.add(&aj.div(&c, n, NEAR), n, NEAR);
if c.is_zero() {
c = tiny.clone();
}
let delta = c.mul(&d, n, NEAR);
f = f.mul(&delta, n, NEAR);
let conv = delta.sub(&one, n, NEAR).abs().partial_cmp(&stop) == Some(Ordering::Less);
j += 1;
if conv || j > cap {
break;
}
}
let sqrtpi = pi_at(n).sqrt(n, NEAR);
let em = a.mul(a, n, NEAR).neg().exp(n, NEAR);
em.div(&sqrtpi, n, NEAR).mul(&f, n, NEAR).round(w, NEAR)
}
fn zeta_at(s: &Float, w: u64) -> Float {
let nt = ((w + 16) * 2 / 5 + 4) as i128;
let mut dprev = Int::from_i64(2);
let mut dcur = Int::from_i64(6);
let six = Int::from_i64(6);
for _ in 2..=nt {
let next = dcur.mul(&six).sub(&dprev);
dprev = dcur;
dcur = next;
}
let d_int = dcur.div_trunc(&Int::from_i64(2));
let df = Float::from_int(&d_int, w, NEAR);
let neg_s = s.neg();
let ln2 = ln2_at(w);
let mut b = Float::from_int(&Int::MINUS_ONE, w, NEAR);
let mut c = df.neg();
let mut sum = Float::zero(w);
for k in 0..nt {
c = b.sub(&c, w, NEAR);
let base = Float::from_int(&Int::from_i128(k + 1), w, NEAR);
let ak = neg_s.mul(&base.ln(w, NEAR), w, NEAR).exp(w, NEAR);
sum = sum.add(&c.mul(&ak, w, NEAR), w, NEAR);
let num = (k + nt) * (k - nt) * 2;
let den = (2 * k + 1) * (k + 1);
b = b
.mul(&Float::from_int(&Int::from_i128(num), w, NEAR), w, NEAR)
.div(&Float::from_int(&Int::from_i128(den), w, NEAR), w, NEAR);
}
let eta = sum.div(&df, w, NEAR);
let one = iflt(1, w);
let two_pow = one.sub(s, w, NEAR).mul(&ln2, w, NEAR).exp(w, NEAR);
let factor = one.sub(&two_pow, w, NEAR);
eta.div(&factor, w, NEAR)
}
fn float_msb(x: &Float) -> i64 {
match &x.repr {
Repr::Normal { sig, exp, .. } => exp + sig.bit_len() as i64 - 1,
_ => i64::MIN,
}
}
struct BernoulliTable {
b: alloc::vec::Vec<Rational>,
}
impl BernoulliTable {
fn new() -> Self {
BernoulliTable {
b: alloc::vec![Rational::from_integer(Int::ONE)], }
}
fn even(&mut self, k: u64) -> Rational {
let idx = (2 * k) as usize;
while self.b.len() <= idx {
let m = self.b.len() as u64; if m > 1 && m % 2 == 1 {
self.b.push(Rational::from_integer(Int::ZERO));
continue;
}
let mut sum = Rational::from_integer(Int::ZERO);
let mut c = Int::ONE; for j in 0..m as usize {
sum = sum.add(&self.b[j].mul(&Rational::from_integer(c.clone())));
c = c
.mul(&Int::from_u64(m + 1 - j as u64))
.div_trunc(&Int::from_u64(j as u64 + 1));
}
let bm = sum.neg().div(&Rational::from_integer(Int::from_u64(m + 1)));
self.b.push(bm);
}
self.b[idx].clone()
}
}
fn stirling_tail(z: &Float, w: u64) -> Float {
let z2 = z.mul(z, w, NEAR);
let mut zpow = z.clone(); let mut sum = Float::zero(w);
let mut table = BernoulliTable::new();
let mut prev_msb = i64::MAX;
let mut k = 1u64;
loop {
let denom = Int::from_u64(2 * k).mul(&Int::from_u64(2 * k - 1));
let coeff = table.even(k).div(&Rational::from_integer(denom));
let term = Float::from_rational(&coeff, w, NEAR).div(&zpow, w, NEAR);
let msb = float_msb(&term);
if msb < -(w as i64) {
break;
}
if msb >= prev_msb {
break;
}
prev_msb = msb;
sum = sum.add(&term, w, NEAR);
zpow = zpow.mul(&z2, w, NEAR); k += 1;
}
sum
}
fn ln_gamma_at(x: &Float, w: u64) -> Float {
let bw = 64 - w.leading_zeros() as u64;
let n = w + 2 * bw + 48;
let z_thresh = (n / 4 + 8) as i64;
let fx = x.floor().and_then(|i| i.to_i64()).unwrap_or(z_thresh);
let m = if fx >= z_thresh { 0 } else { z_thresh - fx };
let z = x.add(&iflt(m, n), n, NEAR);
let mut p = iflt(1, n);
for j in 0..m {
p = p.mul(&x.add(&iflt(j, n), n, NEAR), n, NEAR);
}
let ln_p = if m > 0 { p.ln(n, NEAR) } else { Float::zero(n) };
let ln_z = z.ln(n, NEAR);
let half = rflt(1, 2, n);
let ln_2pi_half = pi_at(n).scale_pow2(1).ln(n, NEAR).scale_pow2(-1);
let ln_gamma_z = z
.sub(&half, n, NEAR)
.mul(&ln_z, n, NEAR)
.sub(&z, n, NEAR)
.add(&ln_2pi_half, n, NEAR)
.add(&stirling_tail(&z, n), n, NEAR);
ln_gamma_z.sub(&ln_p, n, NEAR).round(w, NEAR)
}
fn gamma_pos_at(x: &Float, w: u64) -> Float {
let extra = float_msb(x).max(0) as u64 + 40;
let ln_g = ln_gamma_at(x, w + extra);
ln_g.exp(w, NEAR)
}
fn gamma_fn_at(x: &Float, w: u64) -> Float {
let half = rflt(1, 2, w + 8);
if x.partial_cmp(&half) == Some(Ordering::Less) {
let n = w + 40 + float_msb(x).max(0) as u64;
let one = iflt(1, n);
let one_minus_x = one.sub(x, n, NEAR);
let g1 = gamma_pos_at(&one_minus_x, n);
let sin_pix = pi_at(n).mul(x, n, NEAR).sin(n, NEAR);
return pi_at(n)
.div(&sin_pix.mul(&g1, n, NEAR), n, NEAR)
.round(w, NEAR);
}
gamma_pos_at(x, w)
}
const BESSEL_RECT_THRESHOLD: u64 = 512;
fn bessel_series_at(n: u64, x: &Float, w: u64, alternating: bool) -> Float {
let ax_floor = x.abs().floor().and_then(|i| i.to_i64()).unwrap_or(i64::MAX);
let x_guard = (((ax_floor as u128 + 1) * 185 / 128).min(u64::MAX as u128)) as u64;
let ns = w + x_guard + 64;
let half = x.scale_pow2(-1);
let hs = scaled_int(&half, ns as i64);
let h2 = hs.square().div_2k_trunc(ns as u32); let sum = if w >= BESSEL_RECT_THRESHOLD {
bessel_bracket_rect(n, &h2, ns, alternating)
} else {
bessel_bracket_simple(n, &h2, ns, alternating)
};
let uf = Float::round_raw(sum.is_negative(), sum.magnitude(), -(ns as i64), ns, NEAR).0;
let pref = float_powi(&half, n, ns).div(&factorial_float(n, ns), ns, NEAR);
uf.mul(&pref, ns, NEAR).round(w, NEAR)
}
fn bessel_bracket_simple(n: u64, h2: &Int, ns: u64, alternating: bool) -> Int {
let scale = Int::ONE.mul_2k(ns as u32);
let mut c = scale.clone();
let mut sum = scale.clone();
let mut m = 1u64;
loop {
let divisor = Int::from_u128(m as u128 * (m as u128 + n as u128));
c = c.mul(h2).div_2k_trunc(ns as u32).div_trunc(&divisor);
if c.is_zero() {
break;
}
if alternating && m % 2 == 1 {
sum = sum.sub(&c);
} else {
sum = sum.add(&c);
}
m += 1;
}
sum
}
fn bessel_bracket_rect(n: u64, h2: &Int, ns: u64, alternating: bool) -> Int {
let z = h2.magnitude();
if z.is_zero() {
return Int::ONE.mul_2k(ns as u32);
}
let q =
|k: u64| -> u64 { ((k as u128) * (k as u128 + n as u128)).min(u64::MAX as u128) as u64 };
let t = series_term_count(&z, ns, |_| 1, q);
let c = rect_block_width(t);
let powers = scaled_powers(&z, ns, c);
power_series_rect_signed(ns, c, &powers, alternating, false, |_| 1, q)
}
fn float_powi(base: &Float, e: u64, w: u64) -> Float {
let mut acc = iflt(1, w);
let mut b = base.clone();
let mut e = e;
while e > 0 {
if e & 1 == 1 {
acc = acc.mul(&b, w, NEAR);
}
e >>= 1;
if e > 0 {
b = b.mul(&b, w, NEAR);
}
}
acc
}
fn factorial_float(n: u64, w: u64) -> Float {
Float::from_int(&Int::factorial(n), w, NEAR)
}
fn factorial_int(n: u64) -> Int {
Int::factorial(n)
}
fn harmonic_float(n: u64, w: u64) -> Float {
let mut s = Float::zero(w);
for j in 1..=n {
s = s.add(&iflt(1, w).div(&iflt(j as i64, w), w, NEAR), w, NEAR);
}
s
}
fn is_nonpos_int(x: &Float) -> bool {
match x.to_rational() {
Some(r) => r.denominator() == &Int::ONE && !r.numerator().is_positive(),
None => false,
}
}
fn digamma_tail(z: &Float, w: u64) -> Float {
let z2 = z.mul(z, w, NEAR);
let mut zpow = z2.clone(); let mut sum = Float::zero(w);
let mut table = BernoulliTable::new();
let mut prev_msb = i64::MAX;
let mut k = 1u64;
loop {
let coeff = table
.even(k)
.div(&Rational::from_integer(Int::from_u64(2 * k)));
let term = Float::from_rational(&coeff, w, NEAR).div(&zpow, w, NEAR);
let msb = float_msb(&term);
if msb < -(w as i64) {
break;
}
if msb >= prev_msb {
break;
}
prev_msb = msb;
sum = sum.add(&term, w, NEAR);
zpow = zpow.mul(&z2, w, NEAR); k += 1;
}
sum
}
fn digamma_pos_at(x: &Float, w: u64) -> Float {
let bw = 64 - w.leading_zeros() as u64;
let n = w + 2 * bw + 48;
let z_thresh = (n / 4 + 8) as i64;
let fx = x.floor().and_then(|i| i.to_i64()).unwrap_or(z_thresh);
let m = if fx >= z_thresh { 0 } else { z_thresh - fx };
let z = x.add(&iflt(m, n), n, NEAR);
let mut s = Float::zero(n);
for j in 0..m {
let d = x.add(&iflt(j, n), n, NEAR);
s = s.add(&iflt(1, n).div(&d, n, NEAR), n, NEAR);
}
let ln_z = z.ln(n, NEAR);
let half_over_z = iflt(1, n).div(&z, n, NEAR).scale_pow2(-1);
let psi_z = ln_z
.sub(&half_over_z, n, NEAR)
.sub(&digamma_tail(&z, n), n, NEAR);
psi_z.sub(&s, n, NEAR).round(w, NEAR)
}
fn digamma_at(x: &Float, w: u64) -> Float {
let half = rflt(1, 2, w + 8);
if x.partial_cmp(&half) == Some(Ordering::Less) {
let n = w + 48 + float_msb(x).max(0) as u64;
let one = iflt(1, n);
let one_minus_x = one.sub(x, n, NEAR);
let psi1 = digamma_pos_at(&one_minus_x, n);
let pix = pi_at(n).mul(x, n, NEAR);
let cot = pix.cos(n, NEAR).div(&pix.sin(n, NEAR), n, NEAR);
let pcot = pi_at(n).mul(&cot, n, NEAR);
return psi1.sub(&pcot, n, NEAR).round(w, NEAR);
}
digamma_pos_at(x, w)
}
fn polygamma_asymp(order: u64, z: &Float, w: u64) -> Float {
let n = order;
let zn = float_powi(z, n, w); let znp1 = zn.mul(z, w, NEAR); let mut acc = Float::from_int(&factorial_int(n - 1), w, NEAR).div(&zn, w, NEAR);
acc = acc.add(
&Float::from_int(&factorial_int(n), w, NEAR)
.div(&znp1, w, NEAR)
.scale_pow2(-1),
w,
NEAR,
);
let z2 = z.mul(z, w, NEAR);
let mut zpow = znp1.mul(z, w, NEAR); let mut table = BernoulliTable::new();
let mut prev_msb = i64::MAX;
let mut k = 1u64;
loop {
let mut ratio = Int::ONE;
for i in 1..n {
ratio = ratio.mul(&Int::from_u64(2 * k + i));
}
let coeff = table.even(k).mul(&Rational::from_integer(ratio));
let term = Float::from_rational(&coeff, w, NEAR).div(&zpow, w, NEAR);
let msb = float_msb(&term);
if msb < -(w as i64) {
break;
}
if msb >= prev_msb {
break;
}
prev_msb = msb;
acc = acc.add(&term, w, NEAR);
zpow = zpow.mul(&z2, w, NEAR);
k += 1;
}
if n % 2 == 1 { acc } else { acc.neg() }
}
fn polygamma_at(order: u64, x: &Float, w: u64) -> Float {
let bw = 64 - w.leading_zeros() as u64;
let n = w + 2 * bw + 48;
let z_thresh = ((n / 4 + 8) as i64).max(2 * order as i64 + 8);
let fx = x.floor().and_then(|i| i.to_i64()).unwrap_or(z_thresh);
let m = if fx >= z_thresh { 0 } else { z_thresh - fx };
let z = x.add(&iflt(m, n), n, NEAR);
let psi_z = polygamma_asymp(order, &z, n);
let fact = Float::from_int(&factorial_int(order), n, NEAR);
let mut s = Float::zero(n);
for j in 0..m {
let d = x.add(&iflt(j, n), n, NEAR);
let dpow = float_powi(&d, order + 1, n);
s = s.add(&fact.div(&dpow, n, NEAR), n, NEAR);
}
let raw = if order.is_multiple_of(2) {
psi_z.sub(&s, n, NEAR)
} else {
psi_z.add(&s, n, NEAR)
};
raw.round(w, NEAR)
}
fn signed_ln_gamma_at(x: &Float, w: u64) -> (bool, Float) {
if x.sign() == Sign::Positive {
return (false, ln_gamma_at(x, w));
}
let n = w + 48 + float_msb(x).max(0) as u64;
let one = iflt(1, n);
let one_minus_x = one.sub(x, n, NEAR);
let lg = ln_gamma_at(&one_minus_x, n);
let sinpix = pi_at(n).mul(x, n, NEAR).sin(n, NEAR);
let ln_pi = pi_at(n).ln(n, NEAR);
let ln_abs_sin = sinpix.abs().ln(n, NEAR);
let lnabs = ln_pi
.sub(&ln_abs_sin, n, NEAR)
.sub(&lg, n, NEAR)
.round(w, NEAR);
(sinpix.is_sign_negative(), lnabs)
}
fn beta_at(a: &Float, b: &Float, w: u64) -> Float {
let n = w + 16;
let ab = a.add(b, n, NEAR);
let (sa, la) = signed_ln_gamma_at(a, n);
let (sb, lb) = signed_ln_gamma_at(b, n);
let (sab, lab) = signed_ln_gamma_at(&ab, n);
let v = la.add(&lb, n, NEAR).sub(&lab, n, NEAR);
let mag = v.exp(n, NEAR);
let neg = sa ^ sb ^ sab;
if neg { mag.neg() } else { mag }.round(w, NEAR)
}
fn bessel_y_at(n: u64, x: &Float, w: u64) -> Float {
let ax_floor = x.abs().floor().and_then(|i| i.to_i64()).unwrap_or(i64::MAX);
let x_guard = (((ax_floor as u128 + 1) * 185 / 128).min(u64::MAX as u128)) as u64;
let ns = w + x_guard + n.saturating_mul(4) + 96;
let half = x.scale_pow2(-1); let h2 = half.mul(&half, ns, NEAR); let ln_half = half.ln(ns, NEAR);
let jn = bessel_series_at(n, x, ns, true); let pi = pi_at(ns);
let term_a = ln_half.mul(&jn, ns, NEAR).scale_pow2(1).div(&pi, ns, NEAR);
let half_neg_n = if n == 0 {
iflt(1, ns)
} else {
iflt(1, ns).div(&float_powi(&half, n, ns), ns, NEAR)
};
let mut s1 = Float::zero(ns);
let mut hp = iflt(1, ns); for k in 0..n {
let coef = Float::from_int(&factorial_int(n - k - 1), ns, NEAR).div(
&Float::from_int(&factorial_int(k), ns, NEAR),
ns,
NEAR,
);
s1 = s1.add(&coef.mul(&hp, ns, NEAR), ns, NEAR);
hp = hp.mul(&h2, ns, NEAR);
}
let s1 = s1.mul(&half_neg_n, ns, NEAR);
let two_gamma = gamma_at(ns).scale_pow2(1);
let mut e =
float_powi(&half, n, ns).div(&Float::from_int(&factorial_int(n), ns, NEAR), ns, NEAR);
let mut hk = Float::zero(ns); let mut hnk = harmonic_float(n, ns); let mut s2 = Float::zero(ns);
let kmin = (ax_floor.max(0) as u64) + 2;
let mut k = 0u64;
loop {
let psi = hk.add(&hnk, ns, NEAR).sub(&two_gamma, ns, NEAR);
let mut term = psi.mul(&e, ns, NEAR);
if k % 2 == 1 {
term = term.neg();
}
s2 = s2.add(&term, ns, NEAR);
if k > kmin && float_msb(&term) < -(ns as i64) {
break;
}
k += 1;
hk = hk.add(&iflt(1, ns).div(&iflt(k as i64, ns), ns, NEAR), ns, NEAR);
hnk = hnk.add(
&iflt(1, ns).div(&iflt((n + k) as i64, ns), ns, NEAR),
ns,
NEAR,
);
let denom = Int::from_u128(k as u128 * (n as u128 + k as u128));
e = e
.mul(&h2, ns, NEAR)
.div(&Float::from_int(&denom, ns, NEAR), ns, NEAR);
if e.is_zero() {
break;
}
}
let bracket = s1.add(&s2, ns, NEAR).div(&pi, ns, NEAR);
term_a.sub(&bracket, ns, NEAR).round(w, NEAR)
}
fn bessel_k_at(n: u64, x: &Float, w: u64) -> Float {
let ax_floor = x.abs().floor().and_then(|i| i.to_i64()).unwrap_or(i64::MAX);
let x_guard = (((ax_floor as u128 + 1) * 370 / 128).min(u64::MAX as u128)) as u64;
let ns = w + x_guard + n.saturating_mul(4) + 96;
let half = x.scale_pow2(-1);
let h2 = half.mul(&half, ns, NEAR);
let ln_half = half.ln(ns, NEAR);
let in_ = bessel_series_at(n, x, ns, false); let half_neg_n = if n == 0 {
iflt(1, ns)
} else {
iflt(1, ns).div(&float_powi(&half, n, ns), ns, NEAR)
};
let mut t1 = Float::zero(ns);
let mut hp = iflt(1, ns);
for k in 0..n {
let mut coef = Float::from_int(&factorial_int(n - k - 1), ns, NEAR).div(
&Float::from_int(&factorial_int(k), ns, NEAR),
ns,
NEAR,
);
if k % 2 == 1 {
coef = coef.neg();
}
t1 = t1.add(&coef.mul(&hp, ns, NEAR), ns, NEAR);
hp = hp.mul(&h2, ns, NEAR);
}
let piece1 = t1.mul(&half_neg_n, ns, NEAR).scale_pow2(-1);
let mut piece2 = ln_half.mul(&in_, ns, NEAR);
if n.is_multiple_of(2) {
piece2 = piece2.neg();
}
let two_gamma = gamma_at(ns).scale_pow2(1);
let mut e =
float_powi(&half, n, ns).div(&Float::from_int(&factorial_int(n), ns, NEAR), ns, NEAR);
let mut hk = Float::zero(ns);
let mut hnk = harmonic_float(n, ns);
let mut t2 = Float::zero(ns);
let kmin = (ax_floor.max(0) as u64) + 2;
let mut k = 0u64;
loop {
let psi = hk.add(&hnk, ns, NEAR).sub(&two_gamma, ns, NEAR);
let term = psi.mul(&e, ns, NEAR);
t2 = t2.add(&term, ns, NEAR);
if k > kmin && float_msb(&term) < -(ns as i64) {
break;
}
k += 1;
hk = hk.add(&iflt(1, ns).div(&iflt(k as i64, ns), ns, NEAR), ns, NEAR);
hnk = hnk.add(
&iflt(1, ns).div(&iflt((n + k) as i64, ns), ns, NEAR),
ns,
NEAR,
);
let denom = Int::from_u128(k as u128 * (n as u128 + k as u128));
e = e
.mul(&h2, ns, NEAR)
.div(&Float::from_int(&denom, ns, NEAR), ns, NEAR);
if e.is_zero() {
break;
}
}
let mut piece3 = t2.scale_pow2(-1);
if n % 2 == 1 {
piece3 = piece3.neg();
}
piece1
.add(&piece2, ns, NEAR)
.add(&piece3, ns, NEAR)
.round(w, NEAR)
}
#[cfg(test)]
mod agm_tests {
extern crate std;
use std::println;
use super::*;
use crate::RoundingMode::{AwayFromZero, Nearest, TowardNegative, TowardPositive, TowardZero};
const MODES: [RoundingMode; 5] = [
Nearest,
TowardZero,
TowardPositive,
TowardNegative,
AwayFromZero,
];
fn pi_series_at(w: u64) -> Float {
let n = w + 32;
let a1 = super::atan_recip_scaled(5, n);
let a2 = super::atan_recip_scaled(239, n);
let pi_scaled = a1.shl(4).checked_sub(&a2.shl(2)).unwrap();
Float::round_raw(false, pi_scaled, -(n as i64), w, NEAR).0
}
fn pi_agm_at(w: u64) -> Float {
let n = w + 32 + 2 * ilog2(w);
let mut a = iflt(1, n);
let mut b = rflt(1, 2, n).sqrt(n, NEAR);
let mut t = rflt(1, 4, n);
for p_exp in 0..(2 * ilog2(w) as i64 + 16) {
let a1 = a.add(&b, n, NEAR).scale_pow2(-1);
let b1 = a.mul(&b, n, NEAR).sqrt(n, NEAR);
let diff = a.sub(&a1, n, NEAR);
let d2 = diff.mul(&diff, n, NEAR).scale_pow2(p_exp);
t = t.sub(&d2, n, NEAR);
let conv = a1.sub(&b1, n, NEAR);
a = a1;
b = b1;
if conv.is_zero() || floor_log2(&conv) <= -(w as i64) {
break;
}
}
let s = a.add(&b, n, NEAR);
s.mul(&s, n, NEAR)
.div(&t.scale_pow2(2), n, NEAR)
.round(w, NEAR)
}
fn pi_series(prec: u64, mode: RoundingMode) -> Float {
Float::ziv(prec, mode, pi_series_at)
}
fn pi_agm(prec: u64, mode: RoundingMode) -> Float {
Float::ziv(prec, mode, pi_agm_at)
}
fn ln_series(x: &Float, prec: u64, mode: RoundingMode) -> Float {
let x = x.clone();
Float::ziv(prec, mode, move |w| ln_series_at(&x.round(w, NEAR), w))
}
fn ln_agm(x: &Float, prec: u64, mode: RoundingMode) -> Float {
let x = x.clone();
Float::ziv(prec, mode, move |w| ln_agm_at(&x.round(w, NEAR), w))
}
fn same(a: &Float, b: &Float) -> bool {
a.to_exact_string() == b.to_exact_string()
}
#[test]
fn agm_quick_sanity() {
for &prec in &[53u64, 200, 2000] {
for &mode in &MODES {
assert!(
same(&pi_agm(prec, mode), &pi_series(prec, mode)),
"quick pi_agm != series at {prec} bits, {mode:?}"
);
}
for k in [2i64, 3, 7, 100, 1000] {
let x = iflt(k, prec + 64);
assert!(
same(&ln_agm(&x, prec, Nearest), &ln_series(&x, prec, Nearest)),
"quick ln_agm != series at {prec} bits, x={k}"
);
}
let half = rflt(1, 2, prec + 64); assert!(same(
&ln_agm(&half, prec, Nearest),
&ln_series(&half, prec, Nearest)
));
}
assert!(same(
&ln_agm(&iflt(2, 2064), 2000, Nearest),
&Float::ln2(2000, Nearest)
));
assert!(same(&pi_agm(2000, Nearest), &Float::pi(2000, Nearest)));
}
#[test]
fn ln_near_one_falls_back_to_series() {
let prec = LN_AGM_THRESHOLD + 500; let ref_prec = prec;
let mut xs = alloc::vec::Vec::new();
xs.push(iflt(1, prec + 64));
for k in [10i64, 33, 60, 200] {
let d = iflt(1, prec + 64).scale_pow2(-k);
xs.push(iflt(1, prec + 64).add(&d, prec + 64, NEAR));
xs.push(iflt(1, prec + 64).sub(&d, prec + 64, NEAR));
}
for x in &xs {
for &mode in &MODES {
assert!(
same(&x.ln(prec, mode), &ln_series(x, ref_prec, mode)),
"wired ln != series near 1 at {prec} bits, {mode:?}, x={}",
x.to_exact_string()
);
}
}
}
#[test]
fn ln_agm_matches_series_all_modes() {
let mut state: u64 = 0x1234_5678_9abc_def1;
let mut next = || {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
state
};
for &prec in &[64u64, 777, 9000] {
let cases = if prec >= 40000 { 3 } else { 8 };
for _ in 0..cases {
let mant = Int::from_u64(next() | 1);
let scale = (next() % 81) as i64 - 40;
let x = Float::from_int(&mant, prec + 64, NEAR).scale_pow2(scale);
for &mode in &MODES {
let s = ln_series(&x, prec, mode);
let a = ln_agm(&x, prec, mode);
assert!(
same(&s, &a),
"ln_agm != series at {prec} bits, {mode:?}, x={}",
x.to_exact_string()
);
assert!(
same(&x.ln(prec, mode), &s),
"Float::ln != series at {prec} bits, {mode:?}"
);
}
}
}
}
#[test]
fn ln_agm_known_values() {
for &prec in &[500u64, 3000] {
let two = iflt(2, prec + 64);
assert!(
same(&ln_agm(&two, prec, Nearest), &Float::ln2(prec, Nearest)),
"ln_agm(2) != ln2 at {prec}"
);
let e = Float::e(prec + 64, Nearest);
let one = iflt(1, prec);
assert!(
same(&ln_agm(&e, prec, Nearest), &one),
"ln_agm(e) != 1 at {prec}"
);
}
}
#[test]
#[ignore = "high-precision differential (slow); run with --ignored"]
fn ln_agm_high_precision_matches_series() {
for &prec in &[40000u64, 70000] {
let x = iflt(3, prec + 64).scale_pow2(-1); for &mode in &MODES {
assert!(same(&ln_agm(&x, prec, mode), &ln_series(&x, prec, mode)));
assert!(same(&x.ln(prec, mode), &ln_series(&x, prec, mode)));
}
}
}
#[test]
#[ignore = "timing benchmark; run with --ignored --nocapture"]
fn agm_crossover() {
use std::time::Instant;
fn t<F: Fn() -> Float>(f: F) -> f64 {
let start = Instant::now();
let _ = f();
start.elapsed().as_secs_f64() * 1e3
}
println!("\n== π: Machin series vs Brent–Salamin AGM (ms) ==");
println!(
"{:>10} {:>12} {:>12} {:>8}",
"bits", "series", "agm", "speedup"
);
for &w in &[
1u64 << 12,
1 << 14,
1 << 15,
1 << 16,
1 << 17,
1 << 18,
1 << 19,
] {
let ts = t(|| pi_series_at(w));
let ta = t(|| pi_agm_at(w));
println!("{w:>10} {ts:>12.2} {ta:>12.2} {:>7.2}x", ts / ta);
}
println!("\n== ln: atanh series vs AGM (ms) ==");
println!(
"{:>10} {:>12} {:>12} {:>8}",
"bits", "series", "agm", "speedup"
);
let x = iflt(3, 1 << 20).scale_pow2(-1); for &w in &[
1u64 << 12,
1 << 13,
1 << 14,
1 << 15,
1 << 16,
1 << 17,
1 << 18,
] {
let xw = x.round(w, NEAR);
let ts = t(|| ln_series_at(&xw, w));
let ta = t(|| ln_agm_at(&xw, w));
println!("{w:>10} {ts:>12.2} {ta:>12.2} {:>7.2}x", ts / ta);
}
}
}
#[cfg(test)]
mod sin_cos_rect_tests {
extern crate std;
use std::println;
use super::*;
use crate::RoundingMode::{AwayFromZero, Nearest, TowardNegative, TowardPositive, TowardZero};
const MODES: [RoundingMode; 5] = [
Nearest,
TowardZero,
TowardPositive,
TowardNegative,
AwayFromZero,
];
fn sin_cos_full(x: &Float, prec: u64, mode: RoundingMode, rect: bool) -> (Float, Float) {
let series = move |r: &Float, w: u64| {
if rect {
sin_cos_series_rect(r, w)
} else {
sin_cos_series_simple(r, w)
}
};
let at = move |x: &Float, w: u64| -> (Float, Float) {
let pi = pi_at(w);
let half_pi = pi.scale_pow2(-1);
let q = x.div(&half_pi, w, NEAR).round_half_up_to_int();
let r = x.sub(
&Float::from_int(&q, w, NEAR).mul(&half_pi, w, NEAR),
w,
NEAR,
);
let (sr, cr) = series(&r, w);
let quad = q.rem_euclid(&Int::from_i64(4)).to_i64().unwrap_or(0);
match quad {
0 => (sr, cr),
1 => (cr, sr.neg()),
2 => (sr.neg(), cr.neg()),
_ => (cr.neg(), sr),
}
};
let xs = x.clone();
let at2 = at;
let s = Float::ziv(prec, mode, move |w| at2(&xs.round(w, NEAR), w).0);
let xc = x.clone();
let c = Float::ziv(prec, mode, move |w| at(&xc.round(w, NEAR), w).1);
(s, c)
}
fn bit_identical(a: &Float, b: &Float) -> bool {
a.repr == b.repr && a.precision == b.precision
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn float(&mut self, p: u64, scale: i64) -> Float {
let mant = Int::from_i64((self.next() >> 1) as i64);
let f = Float::from_int(&mant, p, NEAR);
let e = (self.next() % 30) as i64 - 15 + scale;
let f = f.scale_pow2(e);
if self.next() & 1 == 0 { f.neg() } else { f }
}
}
fn differential(precisions: &[u64], per_prec: usize) {
let mut rng = Rng(0x1234_5678_9abc_def1);
for &p in precisions {
for _ in 0..per_prec {
for &scale in &[-20i64, 0, 30] {
let x = rng.float(p, scale);
for &mode in &MODES {
let (ss, sc) = sin_cos_full(&x, p, mode, false);
let (rs, rc) = sin_cos_full(&x, p, mode, true);
assert!(
bit_identical(&ss, &rs),
"sin mismatch p={p} mode={mode:?} x={x:?}"
);
assert!(
bit_identical(&sc, &rc),
"cos mismatch p={p} mode={mode:?} x={x:?}"
);
}
}
}
}
}
#[test]
fn rect_matches_simple_fast() {
differential(&[300, 900, 1024, 1536, 2048], 5);
}
#[test]
fn rect_known_values() {
let p = 2048;
let n = Nearest;
let (s0, c0) = sin_cos_series_rect(&Float::zero(p + 32), p);
assert!(s0.is_zero());
assert!(c0.sub(&iflt(1, p), p, n).is_zero());
let x = Float::from_int(&Int::from_i64(7), p, n).div(
&Float::from_int(&Int::from_i64(10), p, n),
p,
n,
);
let s = x.sin(p, n);
let c = x.cos(p, n);
let one = s.mul(&s, p, n).add(&c.mul(&c, p, n), p, n);
assert!(one.sub(&iflt(1, p), p, n).abs() < rflt(1, 1i64 << 60, p));
let pi6 = Float::pi(p, n).div(&Float::from_int(&Int::from_i64(6), p, n), p, n);
let half = pi6.sin(p, n);
assert!(half.sub(&rflt(1, 2, p), p, n).abs() < rflt(1, 1i64 << 60, p));
}
#[test]
#[ignore = "heavy: run with --release --ignored"]
fn rect_matches_simple_heavy() {
differential(&[3000, 4096, 8192, 16384], 4);
}
#[test]
#[ignore = "benchmark: cargo test --release -- --ignored bench_sin_cos_rect --nocapture"]
fn bench_sin_cos_rect() {
use std::time::Instant;
let n = Nearest;
fn t(iters: u32, mut f: impl FnMut()) -> f64 {
f();
let mut best = f64::MAX;
for _ in 0..3 {
let s = Instant::now();
for _ in 0..iters {
f();
}
best = best.min(s.elapsed().as_secs_f64() / iters as f64);
}
best
}
println!("\n w simple(ms) rect(ms) speedup Cblock");
for &w in &[
256u64, 512, 1024, 1500, 2048, 4096, 8192, 16384, 32768, 65536,
] {
let x = Float::from_int(&Int::from_i64(12345), w + 64, n).div(
&Float::from_int(&Int::from_i64(10000), w + 64, n),
w + 64,
n,
);
let iters: u32 = if w <= 1024 {
200
} else if w <= 4096 {
40
} else if w <= 16384 {
8
} else {
2
};
let z = scaled_int(&x, (w + 32) as i64)
.magnitude()
.square()
.shr(w + 32);
let c = ((2 * sin_cos_term_count(&z, w + 32)) as f64).sqrt().ceil() as u64;
let ts = t(iters, || {
std::hint::black_box(sin_cos_series_simple(&x, w));
});
let tr = t(iters, || {
std::hint::black_box(sin_cos_series_rect(&x, w));
});
println!(
"{w:>6} {:>10.4} {:>10.4} {:>6.2}x {c:>5}",
ts * 1e3,
tr * 1e3,
ts / tr
);
}
}
}
#[cfg(test)]
mod transc_rect_tests {
extern crate std;
use std::println;
use super::*;
use crate::RoundingMode::{AwayFromZero, Nearest, TowardNegative, TowardPositive, TowardZero};
const MODES: [RoundingMode; 5] = [
Nearest,
TowardZero,
TowardPositive,
TowardNegative,
AwayFromZero,
];
fn bit_identical(a: &Float, b: &Float) -> bool {
a.repr == b.repr && a.precision == b.precision
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn frac(&mut self, p: u64, den: u64) -> Float {
let num = 1 + self.next() % (den - 1);
Float::from_int(&Int::from_u64(num), p, NEAR).div(
&Float::from_int(&Int::from_u64(den), p, NEAR),
p,
NEAR,
)
}
}
fn crr(prec: u64, mode: RoundingMode, f: impl Fn(u64) -> Float) -> Float {
Float::ziv(prec, mode, f)
}
fn exp_small(r: &Float, prec: u64, mode: RoundingMode, rect: bool) -> Float {
crr(prec, mode, |w| {
let rs = scaled_int(r, w as i64);
let sum = if rect {
exp_taylor_sum_rect(&rs, w)
} else {
exp_taylor_sum_simple(&rs, w)
};
Float::round_raw(false, sum.magnitude(), -(w as i64), w, NEAR).0
})
}
fn erf_kummer(a: &Float, prec: u64, mode: RoundingMode, rect: bool) -> Float {
crr(prec, mode, |w| {
let as_ = scaled_int(a, w as i64).magnitude();
let sum = if rect {
erf_kummer_sum_rect(&as_, w)
} else {
erf_kummer_sum_simple(&as_, w)
};
Float::round_raw(false, sum, -(w as i64), w, NEAR).0
})
}
fn bessel_forced(
order: u64,
x: &Float,
prec: u64,
mode: RoundingMode,
alternating: bool,
rect: bool,
) -> Float {
crr(prec, mode, |w| {
let ax_floor = x.abs().floor().and_then(|i| i.to_i64()).unwrap_or(i64::MAX);
let x_guard = (((ax_floor as u128 + 1) * 185 / 128).min(u64::MAX as u128)) as u64;
let ns = w + x_guard + 64;
let half = x.scale_pow2(-1);
let hs = scaled_int(&half, ns as i64);
let h2 = hs.square().div_2k_trunc(ns as u32);
let sum = if rect {
bessel_bracket_rect(order, &h2, ns, alternating)
} else {
bessel_bracket_simple(order, &h2, ns, alternating)
};
let uf = Float::round_raw(sum.is_negative(), sum.magnitude(), -(ns as i64), ns, NEAR).0;
let pref = float_powi(&half, order, ns).div(&factorial_float(order, ns), ns, NEAR);
uf.mul(&pref, ns, NEAR).round(w, NEAR)
})
}
fn bessel_differential(precisions: &[u64], per_prec: usize, modes: &[RoundingMode]) {
let mut rng = Rng(0x0be5_5e1c_0de0_1234);
let orders = [0u64, 1, 2, 5];
for &p in precisions {
for _ in 0..per_prec {
for &mode in modes {
let xs = rng.frac(p + 96, 6).mul(
&Float::from_int(&Int::from_i64(12), p + 96, NEAR),
p + 96,
NEAR,
);
let xl = rng.frac(p + 96, 3).mul(
&Float::from_int(&Int::from_i64(36), p + 96, NEAR),
p + 96,
NEAR,
);
let order = orders[(rng.next() as usize) % orders.len()];
for x in [&xs, &xl] {
for alternating in [true, false] {
let s = bessel_forced(order, x, p, mode, alternating, false);
let r = bessel_forced(order, x, p, mode, alternating, true);
assert!(
bit_identical(&s, &r),
"bessel alt={alternating} n={order} p={p} mode={mode:?}"
);
}
}
}
}
}
}
fn differential(precisions: &[u64], per_prec: usize) {
differential_modes(precisions, per_prec, &MODES);
}
fn differential_modes(precisions: &[u64], per_prec: usize, modes: &[RoundingMode]) {
let mut rng = Rng(0x0bad_c0de_1234_5678);
for &p in precisions {
for _ in 0..per_prec {
for &mode in modes {
let xa = rng.frac(p + 96, 3);
let s = crr(p, mode, |w| atanh_series_simple(&xa, w));
let r = crr(p, mode, |w| odd_series_rect(&xa, w, false));
assert!(bit_identical(&s, &r), "atanh p={p} mode={mode:?}");
let xt = rng.frac(p + 96, 4);
let s = crr(p, mode, |w| atan_series_simple(&xt, w));
let r = crr(p, mode, |w| odd_series_rect(&xt, w, true));
assert!(bit_identical(&s, &r), "atan p={p} mode={mode:?}");
let mut re = rng.frac(p + 96, 1 << 20).scale_pow2(-1);
if rng.next() & 1 == 0 {
re = re.neg();
}
let s = exp_small(&re, p, mode, false);
let r = exp_small(&re, p, mode, true);
assert!(bit_identical(&s, &r), "exp p={p} mode={mode:?}");
let a = rng.frac(p + 96, 3000).mul(
&Float::from_int(&Int::from_i64(3), p + 96, NEAR),
p + 96,
NEAR,
);
let s = erf_kummer(&a, p, mode, false);
let r = erf_kummer(&a, p, mode, true);
assert!(bit_identical(&s, &r), "erf p={p} mode={mode:?}");
}
}
}
}
#[test]
fn rect_matches_simple_fast() {
differential(&[256, 640, 1200], 2);
}
#[test]
#[ignore = "heavy: run with --release --ignored"]
fn rect_matches_simple_heavy() {
differential(&[1536, 2048, 3072, 4096, 12000, 48000], 3);
}
#[test]
fn bessel_rect_matches_simple_fast() {
bessel_differential(&[256, 640, 1200], 2, &MODES);
}
#[test]
#[ignore = "heavy: run with --release --ignored"]
fn bessel_rect_matches_simple_heavy() {
bessel_differential(&[1024, 1536, 3072, 8192, 32000], 3, &MODES);
}
#[test]
fn known_values() {
let p = 4096; let n = Nearest;
let e = iflt(1, p).exp(p, n);
assert!(bit_identical(&e.ln(p, n), &iflt(1, p)));
let at1 = iflt(1, p).atan(p, n);
let pi4 = Float::pi(p, n).scale_pow2(-2);
assert!(at1.sub(&pi4, p, n).abs() < rflt(1, 1i64 << 60, p));
let x = rflt(7, 10, p);
assert!(x.exp(p, n).ln(p, n).sub(&x, p, n).abs() < rflt(1, 1i64 << 60, p));
assert!(Float::zero(p).erf(p, n).is_zero());
let erf1 = iflt(1, p).erf(p, n);
assert!(erf1 > rflt(842, 1000, p) && erf1 < rflt(843, 1000, p));
assert!(bit_identical(
&Float::zero(p).bessel_j(0, p, n),
&iflt(1, p)
));
assert!(bit_identical(
&Float::zero(p).bessel_i(0, p, n),
&iflt(1, p)
));
assert!(Float::zero(p).bessel_j(3, p, n).is_zero());
assert!(Float::zero(p).bessel_i(2, p, n).is_zero());
let j0 = iflt(1, p).bessel_j(0, p, n);
assert!(j0 > rflt(76519, 100000, p) && j0 < rflt(76520, 100000, p));
let i0 = iflt(1, p).bessel_i(0, p, n);
assert!(i0 > rflt(126606, 100000, p) && i0 < rflt(126607, 100000, p));
let j1_5 = iflt(5, p).bessel_j(1, p, n);
assert!(j1_5 < rflt(-32757, 100000, p) && j1_5 > rflt(-32758, 100000, p));
}
#[test]
#[ignore = "benchmark: cargo test --release -- --ignored bench_transc_rect --nocapture"]
fn bench_transc_rect() {
use std::time::Instant;
fn t(iters: u32, mut f: impl FnMut()) -> f64 {
f();
let mut best = f64::MAX;
for _ in 0..3 {
let s = Instant::now();
for _ in 0..iters {
f();
}
best = best.min(s.elapsed().as_secs_f64() / iters as f64);
}
best
}
let widths = [
256u64, 512, 1024, 1536, 2048, 3072, 4096, 8192, 16384, 65536,
];
for &w in &widths {
let iters: u32 = if w <= 1024 {
200
} else if w <= 4096 {
40
} else if w <= 16384 {
8
} else {
2
};
let xa = rflt(1, 3, w + 96);
let xt = rflt(1, 4, w + 96);
let re = rflt(1, 3, w + 96); let a = rflt(3, 2, w + 96); let rows: [(&str, f64, f64); 4] = [
(
"atanh",
t(iters, || {
std::hint::black_box(atanh_series_simple(&xa, w));
}),
t(iters, || {
std::hint::black_box(odd_series_rect(&xa, w, false));
}),
),
(
"atan ",
t(iters, || {
std::hint::black_box(atan_series_simple(&xt, w));
}),
t(iters, || {
std::hint::black_box(odd_series_rect(&xt, w, true));
}),
),
(
"exp ",
{
let rs = scaled_int(&re, (w + 16) as i64);
t(iters, || {
std::hint::black_box(exp_taylor_sum_simple(&rs, w + 16));
})
},
{
let rs = scaled_int(&re, (w + 16) as i64);
t(iters, || {
std::hint::black_box(exp_taylor_sum_rect(&rs, w + 16));
})
},
),
(
"erf ",
{
let as_ = scaled_int(&a, (w + 32) as i64).magnitude();
t(iters, || {
std::hint::black_box(erf_kummer_sum_simple(&as_, w + 32));
})
},
{
let as_ = scaled_int(&a, (w + 32) as i64).magnitude();
t(iters, || {
std::hint::black_box(erf_kummer_sum_rect(&as_, w + 32));
})
},
),
];
for (name, ts, tr) in rows {
println!(
"{name} w={w:>6} simple {:>9.4}ms rect {:>9.4}ms {:>5.2}x",
ts * 1e3,
tr * 1e3,
ts / tr
);
}
}
}
#[test]
#[ignore = "benchmark: cargo test --release -- --ignored bench_bessel_rect --nocapture"]
fn bench_bessel_rect() {
use std::time::Instant;
fn t(iters: u32, mut f: impl FnMut()) -> f64 {
f();
let mut best = f64::MAX;
for _ in 0..3 {
let s = Instant::now();
for _ in 0..iters {
f();
}
best = best.min(s.elapsed().as_secs_f64() / iters as f64);
}
best
}
let widths = [256u64, 512, 768, 1024, 1536, 2048, 4096, 8192, 16384, 65536];
let cases: [(&str, i64, u64, bool); 4] = [
("J0 x=2 ", 2, 0, true),
("J2 x=15", 15, 2, true),
("I0 x=2 ", 2, 0, false),
("I0 x=15", 15, 0, false),
];
for &w in &widths {
let iters: u32 = if w <= 1024 {
100
} else if w <= 4096 {
30
} else if w <= 16384 {
6
} else {
2
};
for (name, xi, order, alternating) in cases {
let ax = (xi as u128 * 185 / 128 + 1) as u64;
let ns = w + ax + 64;
let x = iflt(xi, ns);
let half = x.scale_pow2(-1);
let h2 = scaled_int(&half, ns as i64)
.square()
.div_2k_trunc(ns as u32);
let ts = t(iters, || {
std::hint::black_box(bessel_bracket_simple(order, &h2, ns, alternating));
});
let tr = t(iters, || {
std::hint::black_box(bessel_bracket_rect(order, &h2, ns, alternating));
});
println!(
"{name} w={w:>6} simple {:>9.4}ms rect {:>9.4}ms {:>5.2}x",
ts * 1e3,
tr * 1e3,
ts / tr
);
}
}
}
}