use std::sync::Arc;
use num_bigint::BigInt;
use num_traits::One;
use parking_lot::RwLock;
use crate::binary::{
Binary, Bounds, FiniteBounds, ReciprocalRounding, UBinary, reciprocal_of_biguint,
};
use crate::binary_utils::bisection::normalize_finite_to_bounds;
use crate::computable::Computable;
use crate::error::ComputableError;
use crate::node::{Node, NodeOp};
use crate::sane::Sane;
pub(crate) const INITIAL_PI_TERMS: usize = 10;
fn bit_length(x: Sane) -> Sane {
let bits = usize::BITS
.checked_sub(x.0.leading_zeros())
.unwrap_or_else(|| unreachable!("leading_zeros() is always <= usize::BITS"));
Sane(usize::try_from(bits).unwrap_or(0))
}
fn precision_bits_for_num_terms(num_terms: usize) -> usize {
crate::sane_arithmetic!(num_terms; {
let n = num_terms;
let two_n_plus_1 = 2 * n + 1;
let taylor_bits = two_n_plus_1 * 3;
let rounding_margin = bit_length(n + 2) + bit_length(two_n_plus_1);
taylor_bits + rounding_margin
})
}
pub fn pi() -> Computable {
let node = Node::new(Arc::new(PiOp {
num_terms: RwLock::new(INITIAL_PI_TERMS),
}));
Computable::from_node(node)
}
pub fn pi_bounds_at_precision(precision_bits: usize) -> (Binary, Binary) {
let num_terms = crate::sane_arithmetic!(precision_bits; (precision_bits + 10) / 4).max(5);
let rounding_error_precision = crate::sane_arithmetic!(precision_bits, num_terms;
precision_bits + bit_length(num_terms + 2) + bit_length(2 * num_terms + 1));
let reciprocal_precision =
precision_bits_for_num_terms(num_terms).max(rounding_error_precision);
compute_pi_bounds(num_terms, reciprocal_precision)
}
pub struct PiOp {
pub num_terms: RwLock<usize>,
}
impl NodeOp for PiOp {
fn compute_bounds(&self) -> Result<Bounds, ComputableError> {
let num_terms = *self.num_terms.read();
let precision_bits = precision_bits_for_num_terms(num_terms);
let (pi_lo, pi_hi) = compute_pi_bounds(num_terms, precision_bits);
let finite = FiniteBounds::new(pi_lo, pi_hi);
normalize_finite_to_bounds(&finite)
}
fn refine_step(&self, precision_bits: usize) -> Result<bool, ComputableError> {
let mut num_terms = self.num_terms.write();
if precision_bits <= crate::MAX_COMPUTATION_BITS {
let needed = crate::sane_arithmetic!(precision_bits; (precision_bits + 10) / 4).max(1);
if needed > *num_terms {
*num_terms = needed;
return Ok(true);
}
}
*num_terms = (*num_terms).saturating_mul(2).max(1_usize);
Ok(true)
}
fn children(&self) -> Vec<Arc<Node>> {
Vec::new()
}
fn is_refiner(&self) -> bool {
true
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RoundDir {
Down,
Up,
}
fn compute_pi_bounds(num_terms: usize, precision_bits: usize) -> (Binary, Binary) {
let (atan_5_lo, atan_5_hi) = arctan_recip_bounds(5, num_terms, precision_bits);
let (atan_239_lo, atan_239_hi) = arctan_recip_bounds(239, num_terms, precision_bits);
let sixteen = Binary::new(BigInt::from(1_i32), BigInt::from(4_i32)); let four = Binary::new(BigInt::from(1_i32), BigInt::from(2_i32));
let term1_lo = atan_5_lo.mul(&sixteen);
let term1_hi = atan_5_hi.mul(&sixteen);
let term2_lo = atan_239_lo.mul(&four);
let term2_hi = atan_239_hi.mul(&four);
let pi_lo = term1_lo.sub(&term2_hi);
let pi_hi = term1_hi.sub(&term2_lo);
(pi_lo, pi_hi)
}
fn arctan_recip_bounds(k: u64, num_terms: usize, precision_bits: usize) -> (Binary, Binary) {
let k_big = BigInt::from(k);
if num_terms == 0 {
let error =
reciprocal_of_biguint(k_big.magnitude(), precision_bits, ReciprocalRounding::Ceil);
return (error.neg(), error);
}
let k_squared = &k_big * &k_big;
let mut sum_lo = Binary::zero();
let mut sum_hi = Binary::zero();
let mut k_power = k_big;
for i in 0..num_terms {
let coeff = BigInt::from(i) * 2_i64 + 1_i64; let denominator = &coeff * &k_power;
let is_positive_term = i % 2 == 0;
let term_lo = divide_one_by_bigint(
&denominator,
RoundDir::Down,
is_positive_term,
precision_bits,
);
let term_hi =
divide_one_by_bigint(&denominator, RoundDir::Up, is_positive_term, precision_bits);
sum_lo = sum_lo.add(&term_lo);
sum_hi = sum_hi.add(&term_hi);
k_power = &k_power * &k_squared;
}
let error_coeff = BigInt::from(crate::sane_arithmetic!(num_terms; 2 * num_terms + 1));
let error_denom = &error_coeff * &k_power;
let error = reciprocal_of_biguint(
error_denom.magnitude(),
precision_bits,
ReciprocalRounding::Ceil,
);
(sum_lo.sub(&error), sum_hi.add(&error))
}
fn divide_one_by_bigint(
denominator: &BigInt,
rounding: RoundDir,
is_positive_term: bool,
precision_bits: usize,
) -> Binary {
let recip_rounding = match (rounding, is_positive_term) {
(RoundDir::Down, true) => ReciprocalRounding::Floor,
(RoundDir::Up, true) => ReciprocalRounding::Ceil,
(RoundDir::Down, false) => ReciprocalRounding::Ceil,
(RoundDir::Up, false) => ReciprocalRounding::Floor,
};
let unsigned_result =
reciprocal_of_biguint(denominator.magnitude(), precision_bits, recip_rounding);
if is_positive_term {
unsigned_result
} else {
unsigned_result.neg()
}
}
pub fn pi_interval_at_precision(precision_bits: usize) -> FiniteBounds {
let (lo, hi) = pi_bounds_at_precision(precision_bits);
FiniteBounds::new(lo, hi)
}
pub fn two_pi_interval_at_precision(precision_bits: usize) -> FiniteBounds {
use num_bigint::BigUint;
let pi_interval = pi_interval_at_precision(precision_bits);
let two = UBinary::new(BigUint::from(1u32), BigInt::from(1_i32)); pi_interval.scale_positive(&two)
}
pub fn half_pi_interval_at_precision(precision_bits: usize) -> FiniteBounds {
let (pi_lo, pi_hi) = pi_bounds_at_precision(precision_bits);
let half_pi_lo = Binary::new(pi_lo.mantissa().clone(), pi_lo.exponent() - BigInt::one());
let half_pi_hi = Binary::new(pi_hi.mantissa().clone(), pi_hi.exponent() - BigInt::one());
FiniteBounds::new(half_pi_lo, half_pi_hi)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binary::XBinary;
use crate::refinement::XUsize;
fn bin(mantissa: i64, exponent: i64) -> Binary {
Binary::new(BigInt::from(mantissa), BigInt::from(exponent))
}
fn epsilon_as_binary(n: usize) -> Binary {
Binary::new(BigInt::from(1), BigInt::from(-(n as i64)))
}
fn unwrap_finite(x: &XBinary) -> Binary {
match x {
XBinary::Finite(b) => b.clone(),
_ => panic!("expected finite"),
}
}
fn pi_f64_binary() -> Binary {
Binary::from_f64(std::f64::consts::PI).expect("PI should convert to Binary")
}
#[test]
fn pi_bounds_contain_true_pi() {
let (pi_lo, pi_hi) = compute_pi_bounds(20, precision_bits_for_num_terms(20));
let pi_f64 = pi_f64_binary();
assert!(pi_lo < pi_hi, "lower bound should be less than upper bound");
assert!(
pi_hi >= pi_f64,
"upper bound should be >= f64 pi approximation"
);
let f64_error_bound = bin(1, -50);
let pi_lo_minus_f64 = pi_lo.sub(&pi_f64);
assert!(
pi_lo_minus_f64 < f64_error_bound,
"lower bound should be within 2^-50 of f64 pi approximation"
);
let width = pi_hi.sub(&pi_lo);
let precision_threshold = bin(1, -40);
assert!(
width < precision_threshold,
"pi bounds with 20 terms should have width < 2^-40"
);
}
#[test]
fn pi_bounds_refine_to_high_precision() {
let (pi_lo_5, pi_hi_5) = compute_pi_bounds(5, precision_bits_for_num_terms(5));
let (pi_lo_20, pi_hi_20) = compute_pi_bounds(20, precision_bits_for_num_terms(20));
let width_5 = pi_hi_5.sub(&pi_lo_5);
let width_20 = pi_hi_20.sub(&pi_lo_20);
assert!(width_20 < width_5, "more terms should give tighter bounds");
}
#[test]
fn pi_computable_refines() {
let pi_comp = pi();
let tolerance_exp = XUsize::Finite(20); let bounds = pi_comp
.refine_to_default(tolerance_exp)
.expect("refine should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let pi_f64 = pi_f64_binary();
assert!(
upper >= pi_f64,
"upper bound should be >= f64 pi approximation"
);
assert!(
lower <= pi_f64,
"lower bound should be <= f64 pi approximation (after simplification)"
);
let width = upper.sub(&lower);
let eps_binary = epsilon_as_binary(20);
assert!(width <= eps_binary, "width should be <= epsilon");
}
#[test]
fn pi_bounds_at_precision_helper() {
const PRECISION_BITS: usize = 50;
let (lo, hi) = pi_bounds_at_precision(PRECISION_BITS);
let width = hi.sub(&lo);
let threshold = bin(1, -(PRECISION_BITS as i64 - 1));
assert!(
width < threshold,
"{} bits of precision should give width < 2^-{}",
PRECISION_BITS,
PRECISION_BITS - 1
);
}
#[test]
fn pi_bounds_at_precision_256_bits() {
let (lo, hi) = pi_bounds_at_precision(256);
let width = hi.sub(&lo);
let threshold = bin(1, -255);
assert!(
width < threshold,
"256 bits of precision should give width < 2^-255, got width = {}",
width
);
let pi_f64 = pi_f64_binary();
assert!(hi >= pi_f64, "upper bound should be >= f64 pi");
}
#[test]
fn pi_computable_refines_beyond_128_bits() {
let pi_comp = pi();
let tolerance_exp = XUsize::Finite(128); let bounds = pi_comp
.refine_to_default(tolerance_exp)
.expect("refine to 2^-128 should succeed");
let lower = unwrap_finite(bounds.small());
let upper = unwrap_finite(&bounds.large());
let width = upper.sub(&lower);
let eps_binary = epsilon_as_binary(128);
assert!(
width <= eps_binary,
"width should be <= 2^-128, got width = {}",
width
);
}
}