use crate::backend::{NiBackend, NiFloat};
pub struct NiStep<F: NiFloat> {
pub n: u64,
pub term: F,
pub sum: F,
}
pub struct NiSeries<B: NiBackend> {
precision_bits: u32,
n: u64,
pi_pow: B::Float,
fact: B::Float,
two_pow: B::Float,
sum: B::Float,
pi: B::Float,
}
impl<B: NiBackend> NiSeries<B> {
pub fn new(precision_bits: u32) -> Self {
let pi = B::pi(precision_bits);
let pi_pow = pi.clone(); let fact = B::from_u64(1, precision_bits); let two_pow = B::from_u64(2, precision_bits); let sum = B::zero(precision_bits);
NiSeries {
precision_bits,
n: 0,
pi_pow,
fact,
two_pow,
sum,
pi,
}
}
}
impl<B: NiBackend> Iterator for NiSeries<B> {
type Item = NiStep<B::Float>;
fn next(&mut self) -> Option<Self::Item> {
let p = self.precision_bits;
self.n += 1;
if self.n > 1 {
self.pi_pow = B::mul(&self.pi_pow, &self.pi, p);
let n_f = B::from_u64(self.n, p);
self.fact = B::mul(&self.fact, &n_f, p);
let step = B::two_pow(2 * self.n as u32 - 1, p);
self.two_pow = B::mul(&self.two_pow, &step, p);
}
let denom = B::mul(&self.fact, &self.two_pow, p);
let term = B::div(&self.pi_pow, &denom, p);
self.sum.add_assign(&term);
Some(NiStep {
n: self.n,
term: term.clone(),
sum: self.sum.clone(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "backend-dashu")]
#[test]
fn series_first_term_is_positive() {
use crate::backend::dashu::DashuBackend;
let step = NiSeries::<DashuBackend>::new(128).next().unwrap();
println!("term f64 = {}", step.term.to_f64());
println!("term str = {}", step.term.to_decimal_string(10));
assert!(step.term.to_f64() > 0.0);
}
#[cfg(feature = "backend-dashu")]
#[test]
fn series_converges_to_known_value() {
use crate::backend::dashu::DashuBackend;
use crate::constants::NI_F64;
let sum = NiSeries::<DashuBackend>::new(256)
.take(20)
.last()
.unwrap()
.sum
.to_f64();
assert!(
(sum - NI_F64).abs() < 1e-14,
"series drift: {}",
(sum - NI_F64).abs()
);
}
}