use super::Surreal;
use crate::linalg::integer::gcd_u128;
use crate::scalar::{Rational, Scalar};
use std::cmp::Ordering;
const SERIES_POWER_LIMIT: usize = 4096;
const SERIES_TERM_BUDGET: usize = 100_000;
impl Surreal {
pub fn inv_to_terms(&self, n: usize) -> Option<Surreal> {
if self.is_zero() {
return None;
}
if n == 0 {
return Some(Surreal::zero());
}
let (e0, c0) = self.terms[0].clone();
let m_inv = Surreal::monomial(e0.neg(), c0.inv()?); let r = m_inv.mul(self).sub(&Surreal::one()); if r.is_zero() {
return Some(m_inv); }
let neg_r = r.neg();
let mut series = Surreal::one();
let mut power = Surreal::one();
for _ in 0..SERIES_POWER_LIMIT {
power = power.mul(&neg_r);
if power.is_zero() {
return Some(m_inv.mul(&series).truncate(n));
}
if power.terms.len() > SERIES_TERM_BUDGET {
return None;
}
if leading_below_known_window(&series, n, &power) {
return Some(m_inv.mul(&series).truncate(n));
}
series = checked_surreal_add(&series, &power)?;
if series.terms.len() > SERIES_TERM_BUDGET {
return None;
}
}
None
}
pub fn sqrt_to_terms(&self, n: usize) -> Option<Surreal> {
self.nth_root_to_terms(2, n)
}
pub fn nth_root_to_terms(&self, k: u128, n: usize) -> Option<Surreal> {
if k == 0 {
return None;
}
if self.is_zero() {
return Some(Surreal::zero());
}
if k.is_multiple_of(2) && self.sign() == Ordering::Less {
return None; }
let (e0, c0) = self.terms[0].clone();
let root_c0 = c0.nth_root(k)?;
let e0_over_k = e0.mul(&Surreal::from_rational(Rational::new(1, k as i128)));
let root_m = Surreal::monomial(e0_over_k, root_c0);
let m_inv = Surreal::monomial(e0.neg(), c0.inv()?);
let r = m_inv.mul(self).sub(&Surreal::one());
if r.is_zero() {
return Some(root_m); }
let alpha = Rational::new(1, k as i128);
let series = binomial_series(&r, alpha, n)?;
Some(root_m.mul(&series).truncate(n))
}
}
fn binomial_series(r: &Surreal, alpha: Rational, n: usize) -> Option<Surreal> {
if n == 0 {
return Some(Surreal::zero());
}
let mut series = Surreal::one();
let mut power = Surreal::one(); let mut coeff = Rational::one(); for j in 1..=SERIES_POWER_LIMIT {
power = power.mul(r);
if power.is_zero() {
return Some(series.truncate(n));
}
if power.terms.len() > SERIES_TERM_BUDGET {
return None;
}
if leading_below_known_window(&series, n, &power) {
return Some(series.truncate(n));
}
coeff = next_binomial_coeff(&coeff, &alpha, j)?;
if coeff.is_zero() {
continue; }
let contrib = checked_surreal_scale(&coeff, &power)?;
series = checked_surreal_add(&series, &contrib)?;
if series.terms.len() > SERIES_TERM_BUDGET {
return None;
}
}
None
}
fn leading_below_known_window(series: &Surreal, n: usize, next_power: &Surreal) -> bool {
n == 0
|| series
.terms
.get(n - 1)
.is_some_and(|(nth_exp, _)| next_power.terms[0].0.cmp(nth_exp) == Ordering::Less)
}
fn checked_rational_div_usize(a: &Rational, d: usize) -> Option<Rational> {
let d = i128::try_from(d).ok()?;
let g = gcd_u128(a.numer().unsigned_abs(), d as u128);
let g = i128::try_from(g).ok()?;
let num = a.numer() / g;
let den_factor = d / g;
Rational::try_new(num, a.denom().checked_mul(den_factor)?)
}
fn rational_sub_usize(a: &Rational, rhs: usize) -> Option<Rational> {
let rhs = i128::try_from(rhs).ok()?;
let scaled_rhs = rhs.checked_mul(a.denom())?;
Rational::try_new(a.numer().checked_sub(scaled_rhs)?, a.denom())
}
fn next_binomial_coeff(prev: &Rational, alpha: &Rational, j: usize) -> Option<Rational> {
let shifted = rational_sub_usize(alpha, j - 1)?;
let num = prev.checked_mul(&shifted)?;
checked_rational_div_usize(&num, j)
}
fn checked_surreal_scale(coeff: &Rational, x: &Surreal) -> Option<Surreal> {
let mut terms = Vec::with_capacity(x.terms.len());
for (exp, c) in &x.terms {
let scaled = coeff.checked_mul(c)?;
if !scaled.is_zero() {
terms.push((exp.clone(), scaled));
}
}
Some(Surreal { terms })
}
fn checked_surreal_add(a: &Surreal, b: &Surreal) -> Option<Surreal> {
let mut terms = Vec::with_capacity(a.terms.len() + b.terms.len());
let mut i = 0;
let mut j = 0;
while i < a.terms.len() && j < b.terms.len() {
match a.terms[i].0.cmp(&b.terms[j].0) {
Ordering::Greater => {
terms.push(a.terms[i].clone());
i += 1;
}
Ordering::Less => {
terms.push(b.terms[j].clone());
j += 1;
}
Ordering::Equal => {
let coeff = a.terms[i].1.checked_add(&b.terms[j].1)?;
if !coeff.is_zero() {
terms.push((a.terms[i].0.clone(), coeff));
}
i += 1;
j += 1;
}
}
}
terms.extend_from_slice(&a.terms[i..]);
terms.extend_from_slice(&b.terms[j..]);
Some(Surreal { terms })
}