use crate::polynomial::{Polynomial, Var};
#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use num_bigint::BigInt;
use num_integer::Integer;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
#[derive(Clone, Debug)]
pub struct AlgebraicNumber {
polynomial: Polynomial,
var: Var,
lower: BigRational,
upper: BigRational,
}
impl AlgebraicNumber {
pub fn new(polynomial: Polynomial, var: Var, lower: BigRational, upper: BigRational) -> Self {
let num_roots = polynomial.count_roots_in_interval(var, &lower, &upper);
assert_eq!(
num_roots, 1,
"Interval must contain exactly one root, found {}",
num_roots
);
Self {
polynomial: polynomial.primitive(),
var,
lower,
upper,
}
}
pub fn from_rational(r: BigRational) -> Self {
let poly = Polynomial::from_var(0).sub(&Polynomial::constant(r.clone()));
Self {
polynomial: poly,
var: 0,
lower: r.clone(),
upper: r,
}
}
pub fn sqrt(n: &BigRational) -> Option<Self> {
if n.is_negative() {
return None;
}
if n.is_zero() {
return Some(Self::from_rational(BigRational::zero()));
}
if let Some(sqrt_n) = crate::polynomial::rational_sqrt(n) {
return Some(Self::from_rational(sqrt_n));
}
let poly =
Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]).sub(&Polynomial::constant(n.clone()));
let roots = poly.isolate_roots(0);
for (lo, hi) in roots {
let mid = (&lo + &hi) / BigRational::from_integer(BigInt::from(2));
if mid.is_positive() {
let adjusted_lo = if lo.is_negative() {
BigRational::zero()
} else {
lo
};
if poly.count_roots_in_interval(0, &adjusted_lo, &hi) == 1 {
return Some(Self::new(poly.clone(), 0, adjusted_lo, hi));
}
}
}
None
}
pub fn polynomial(&self) -> &Polynomial {
&self.polynomial
}
pub fn interval(&self) -> (&BigRational, &BigRational) {
(&self.lower, &self.upper)
}
pub fn var(&self) -> Var {
self.var
}
pub fn refine(&mut self) {
let mid = (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2));
let val_mid = self.polynomial.eval_at(self.var, &mid);
if val_mid.constant_term().is_zero() {
self.lower = mid.clone();
self.upper = mid;
} else {
let val_lo = self.polynomial.eval_at(self.var, &self.lower);
let val_mid = self.polynomial.eval_at(self.var, &mid);
let sign_lo = val_lo.constant_term().signum();
let sign_mid = val_mid.constant_term().signum();
if sign_lo != sign_mid {
self.upper = mid;
} else {
self.lower = mid;
}
}
}
pub fn approximate(&self) -> BigRational {
(&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2))
}
pub fn approximate_with_precision(&mut self, epsilon: &BigRational) -> BigRational {
while &self.upper - &self.lower > *epsilon {
self.refine();
}
self.approximate()
}
pub fn is_zero(&self) -> bool {
self.lower.is_zero() && self.upper.is_zero()
}
pub fn is_positive(&self) -> bool {
self.lower.is_positive()
}
pub fn is_negative(&self) -> bool {
self.upper.is_negative()
}
pub fn is_rational(&self) -> bool {
let degree = self.polynomial.degree(self.var);
degree <= 1 || self.lower == self.upper
}
pub fn sign(&self) -> Option<i8> {
if self.is_zero() {
Some(0)
} else if self.is_positive() {
Some(1)
} else if self.is_negative() {
Some(-1)
} else {
None
}
}
pub fn cmp_algebraic(&mut self, other: &mut AlgebraicNumber) -> Ordering {
let max_iterations = 1000;
let mut iterations = 0;
while iterations < max_iterations {
iterations += 1;
if self.upper < other.lower {
return Ordering::Less;
}
if self.lower > other.upper {
return Ordering::Greater;
}
if self.lower == self.upper && other.lower == other.upper && self.lower == other.lower {
return Ordering::Equal;
}
self.refine();
other.refine();
}
self.approximate().cmp(&other.approximate())
}
pub fn cmp_rational(&mut self, r: &BigRational) -> Ordering {
let max_iterations = 1000;
let mut iterations = 0;
while iterations < max_iterations {
iterations += 1;
if &self.upper < r {
return Ordering::Less;
}
if &self.lower > r {
return Ordering::Greater;
}
if &self.lower == r && &self.upper == r {
return Ordering::Equal;
}
self.refine();
}
self.approximate().cmp(r)
}
pub fn negate(&self) -> AlgebraicNumber {
let negated_poly = negate_polynomial(&self.polynomial, self.var);
AlgebraicNumber {
polynomial: negated_poly,
var: self.var,
lower: -self.upper.clone(),
upper: -self.lower.clone(),
}
}
pub fn add_rational(&self, r: &BigRational) -> AlgebraicNumber {
let shifted_poly = self.polynomial.substitute(
self.var,
&Polynomial::from_var(self.var).sub(&Polynomial::constant(r.clone())),
);
AlgebraicNumber {
polynomial: shifted_poly,
var: self.var,
lower: &self.lower + r,
upper: &self.upper + r,
}
}
pub fn mul_rational(&self, r: &BigRational) -> AlgebraicNumber {
if r.is_zero() {
return Self::from_rational(BigRational::zero());
}
let scaled_poly = scale_polynomial_var(&self.polynomial, self.var, r);
let (new_lower, new_upper) = if r.is_positive() {
(&self.lower * r, &self.upper * r)
} else {
(&self.upper * r, &self.lower * r)
};
AlgebraicNumber {
polynomial: scaled_poly,
var: self.var,
lower: new_lower,
upper: new_upper,
}
}
pub fn sub_rational(&self, r: &BigRational) -> AlgebraicNumber {
self.add_rational(&(-r))
}
pub fn inverse(&self) -> Option<AlgebraicNumber> {
if self.is_zero() {
return None;
}
let inv_poly = reciprocal_polynomial(&self.polynomial, self.var);
let (new_lower, new_upper) = if self.is_positive() || self.is_negative() {
(
BigRational::from_integer(BigInt::from(1)) / &self.upper,
BigRational::from_integer(BigInt::from(1)) / &self.lower,
)
} else {
return None;
};
Some(AlgebraicNumber {
polynomial: inv_poly,
var: self.var,
lower: new_lower,
upper: new_upper,
})
}
pub fn div_rational(&self, r: &BigRational) -> Option<AlgebraicNumber> {
if r.is_zero() {
return None;
}
Some(self.mul_rational(&(BigRational::from_integer(BigInt::from(1)) / r)))
}
pub fn pow(&self, n: i32) -> Option<AlgebraicNumber> {
if n == 0 {
return Some(Self::from_rational(BigRational::from_integer(
BigInt::from(1),
)));
}
if n < 0 {
return self.inverse()?.pow(-n);
}
let mut result = Self::from_rational(BigRational::from_integer(BigInt::from(1)));
let mut base = self.clone();
let mut exp = n as u32;
while exp > 0 {
if exp % 2 == 1 {
let approx = base.approximate() * result.approximate();
result = Self::from_rational(approx);
}
if exp > 1 {
let approx = base.approximate() * base.approximate();
base = Self::from_rational(approx);
}
exp /= 2;
}
Some(result)
}
pub fn add_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
if self.is_rational() {
return other.add_rational(&self.approximate());
}
if other.is_rational() {
return self.add_rational(&other.approximate());
}
let p_y = self
.polynomial
.substitute(self.var, &Polynomial::from_var(Y_VAR));
let z_minus_y = Polynomial::from_var(Z_VAR).sub(&Polynomial::from_var(Y_VAR));
let q_shift = other.polynomial.substitute(other.var, &z_minus_y);
let r = p_y.resultant(&q_shift, Y_VAR).square_free();
combine_via_resultant(self, other, r, Z_VAR, false)
}
pub fn mul_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
if self.is_rational() {
return other.mul_rational(&self.approximate());
}
if other.is_rational() {
return self.mul_rational(&other.approximate());
}
if self.is_zero() || other.is_zero() {
return Self::from_rational(BigRational::zero());
}
let p_y = self
.polynomial
.substitute(self.var, &Polynomial::from_var(Y_VAR));
let q_star = scale_for_product(&other.polynomial, other.var, Z_VAR);
let r = p_y.resultant(&q_star, Y_VAR).square_free();
combine_via_resultant(self, other, r, Z_VAR, true)
}
}
const Z_VAR: Var = 0;
const Y_VAR: Var = 1;
fn combine_via_resultant(
a: &mut AlgebraicNumber,
b: &mut AlgebraicNumber,
r_poly: Polynomial,
z_var: Var,
is_product: bool,
) -> AlgebraicNumber {
for _ in 0..300 {
let (lo, hi) = combined_interval(a, b, is_product);
let lo_is_root = r_poly.eval_at(z_var, &lo).constant_term().is_zero();
let hi_is_root = r_poly.eval_at(z_var, &hi).constant_term().is_zero();
if !lo_is_root && !hi_is_root && r_poly.count_roots_in_interval(z_var, &lo, &hi) == 1 {
if let Some(root) = rational_root_in(&r_poly, z_var, &lo, &hi) {
return AlgebraicNumber::from_rational(root);
}
return AlgebraicNumber::new(r_poly, z_var, lo, hi);
}
a.refine();
b.refine();
}
AlgebraicNumber::from_rational(combined_point(a, b, is_product))
}
fn combined_interval(
a: &AlgebraicNumber,
b: &AlgebraicNumber,
is_product: bool,
) -> (BigRational, BigRational) {
if is_product {
product_bounds(&a.lower, &a.upper, &b.lower, &b.upper)
} else {
(&a.lower + &b.lower, &a.upper + &b.upper)
}
}
fn combined_point(a: &AlgebraicNumber, b: &AlgebraicNumber, is_product: bool) -> BigRational {
if is_product {
a.approximate() * b.approximate()
} else {
a.approximate() + b.approximate()
}
}
fn product_bounds(
a_lo: &BigRational,
a_hi: &BigRational,
b_lo: &BigRational,
b_hi: &BigRational,
) -> (BigRational, BigRational) {
let corners = [a_lo * b_lo, a_lo * b_hi, a_hi * b_lo, a_hi * b_hi];
let mut lo = corners[0].clone();
let mut hi = corners[0].clone();
for c in &corners[1..] {
if *c < lo {
lo = c.clone();
}
if *c > hi {
hi = c.clone();
}
}
(lo, hi)
}
fn rational_root_in(
poly: &Polynomial,
var: Var,
lo: &BigRational,
hi: &BigRational,
) -> Option<BigRational> {
let deg = poly.degree(var);
if deg == 0 {
return None;
}
let coeffs: Vec<BigRational> = (0..=deg).map(|k| poly.univ_coeff(var, k)).collect();
let mut den_lcm = BigInt::one();
for c in &coeffs {
den_lcm = den_lcm.lcm(c.denom());
}
let int_coeffs: Vec<BigInt> = coeffs
.iter()
.map(|c| c.numer() * (&den_lcm / c.denom()))
.collect();
let a0 = &int_coeffs[0];
let an = &int_coeffs[deg as usize];
if a0.is_zero() {
let zero = BigRational::zero();
if lo < &zero && &zero < hi {
return Some(zero);
}
return None;
}
let a0_i64 = i64::try_from(a0.abs()).ok().filter(|&x| x <= 1_000_000)?;
let an_i64 = i64::try_from(an.abs()).ok().filter(|&x| x <= 1_000_000)?;
let num_divs = divisors_i64(a0_i64);
let den_divs = divisors_i64(an_i64);
for &p in &num_divs {
for &q in &den_divs {
for sign in [1i64, -1i64] {
let cand = BigRational::new(BigInt::from(sign * p), BigInt::from(q));
if &cand <= lo || &cand >= hi {
continue;
}
if poly.eval_at(var, &cand).constant_term().is_zero() {
return Some(cand);
}
}
}
}
None
}
fn divisors_i64(n: i64) -> Vec<i64> {
let n = n.abs();
let mut divs = Vec::new();
let mut d = 1i64;
while d.saturating_mul(d) <= n {
if n % d == 0 {
divs.push(d);
if d != n / d {
divs.push(n / d);
}
}
d += 1;
}
divs
}
fn negate_polynomial(p: &Polynomial, var: Var) -> Polynomial {
let terms: Vec<_> = p
.terms()
.iter()
.map(|term| {
let degree = term.monomial.degree(var);
let coeff = if degree % 2 == 1 {
-term.coeff.clone()
} else {
term.coeff.clone()
};
crate::polynomial::Term::new(coeff, term.monomial.clone())
})
.collect();
Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
}
fn scale_polynomial_var(p: &Polynomial, var: Var, r: &BigRational) -> Polynomial {
if r.is_zero() {
return Polynomial::zero();
}
let terms: Vec<_> = p
.terms()
.iter()
.map(|term| {
let degree = term.monomial.degree(var);
let new_coeff = &term.coeff / r.pow(degree as i32);
crate::polynomial::Term::new(new_coeff, term.monomial.clone())
})
.collect();
Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
}
fn reciprocal_polynomial(p: &Polynomial, var: Var) -> Polynomial {
let max_degree = p
.terms()
.iter()
.map(|term| term.monomial.degree(var))
.max()
.unwrap_or(0);
let terms: Vec<_> = p
.terms()
.iter()
.map(|term| {
let degree = term.monomial.degree(var);
let new_powers: Vec<(Var, u32)> = term
.monomial
.vars()
.iter()
.map(|vp| {
if vp.var == var {
(vp.var, max_degree - degree)
} else {
(vp.var, vp.power)
}
})
.collect();
let new_monomial =
if new_powers.is_empty() || (new_powers.len() == 1 && new_powers[0].1 == 0) {
crate::polynomial::Monomial::unit()
} else {
crate::polynomial::Monomial::from_powers(new_powers)
};
crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
})
.collect();
Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
}
#[allow(dead_code)]
fn shift_var(p: &Polynomial, old_var: Var, new_var: Var) -> Polynomial {
if old_var == new_var {
return p.clone();
}
let terms: Vec<_> = p
.terms()
.iter()
.map(|term| {
let new_powers: Vec<(Var, u32)> = term
.monomial
.vars()
.iter()
.map(|vp| {
if vp.var == old_var {
(new_var, vp.power)
} else {
(vp.var, vp.power)
}
})
.collect();
let new_monomial = if new_powers.is_empty() {
crate::polynomial::Monomial::unit()
} else {
crate::polynomial::Monomial::from_powers(new_powers)
};
crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
})
.collect();
Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
}
fn scale_for_product(p: &Polynomial, x_var: Var, z_var: Var) -> Polynomial {
let max_degree = p
.terms()
.iter()
.map(|term| term.monomial.degree(x_var))
.max()
.unwrap_or(0);
let y_var = Y_VAR;
let terms: Vec<_> = p
.terms()
.iter()
.map(|term| {
let x_degree = term.monomial.degree(x_var);
let mut new_powers = Vec::new();
for vp in term.monomial.vars() {
if vp.var != x_var {
new_powers.push((vp.var, vp.power));
}
}
if max_degree > x_degree {
new_powers.push((y_var, max_degree - x_degree));
}
if x_degree > 0 {
new_powers.push((z_var, x_degree));
}
let new_monomial = if new_powers.is_empty() {
crate::polynomial::Monomial::unit()
} else {
crate::polynomial::Monomial::from_powers(new_powers)
};
crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
})
.collect();
Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64) -> BigRational {
BigRational::from_integer(BigInt::from(n))
}
#[test]
fn test_algebraic_from_rational() {
let a = AlgebraicNumber::from_rational(rat(3));
assert!(a.is_positive());
assert_eq!(a.approximate(), rat(3));
}
#[test]
fn test_algebraic_sqrt() {
if let Some(a) = AlgebraicNumber::sqrt(&rat(4)) {
assert_eq!(a.approximate(), rat(2));
} else {
panic!("√4 should return a value");
}
if let Some(mut b) = AlgebraicNumber::sqrt(&rat(2)) {
for _ in 0..20 {
b.refine();
}
let approx = b.approximate();
assert!(approx.is_positive(), "√2 approximation should be positive");
assert!(
approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
"√2 approximation should be less than 2"
);
let poly = b.polynomial();
let val = poly.eval_at(b.var(), &approx);
let constant = val.constant_term().abs();
assert!(
constant < BigRational::from_integer(BigInt::from(1)),
"Polynomial evaluation at approximation should be small, got {}",
constant
);
} else {
panic!("√2 should return a value");
}
}
#[test]
fn test_algebraic_negate() {
let a = AlgebraicNumber::from_rational(rat(3));
let neg_a = a.negate();
assert_eq!(neg_a.approximate(), rat(-3));
}
#[test]
fn test_algebraic_add_rational() {
let a = AlgebraicNumber::from_rational(rat(3));
let b = a.add_rational(&rat(5));
assert_eq!(b.approximate(), rat(8));
}
#[test]
fn test_algebraic_mul_rational() {
let a = AlgebraicNumber::from_rational(rat(3));
let b = a.mul_rational(&rat(4));
assert_eq!(b.approximate(), rat(12));
}
#[test]
fn test_algebraic_cmp_rational() {
let mut a = AlgebraicNumber::from_rational(rat(3));
assert_eq!(a.cmp_rational(&rat(2)), Ordering::Greater);
assert_eq!(a.cmp_rational(&rat(3)), Ordering::Equal);
assert_eq!(a.cmp_rational(&rat(4)), Ordering::Less);
}
#[test]
fn test_algebraic_cmp() {
let mut a = AlgebraicNumber::from_rational(rat(2));
let mut b = AlgebraicNumber::from_rational(rat(3));
assert_eq!(a.cmp_algebraic(&mut b), Ordering::Less);
}
#[test]
fn test_algebraic_sign() {
let a = AlgebraicNumber::from_rational(rat(5));
assert_eq!(a.sign(), Some(1));
let b = AlgebraicNumber::from_rational(rat(-3));
assert_eq!(b.sign(), Some(-1));
let c = AlgebraicNumber::from_rational(rat(0));
assert_eq!(c.sign(), Some(0));
}
#[test]
fn test_algebraic_sub_rational() {
let a = AlgebraicNumber::from_rational(rat(10));
let b = a.sub_rational(&rat(3));
assert_eq!(b.approximate(), rat(7));
let c = AlgebraicNumber::from_rational(rat(5));
let d = c.sub_rational(&rat(8));
assert_eq!(d.approximate(), rat(-3));
}
#[test]
fn test_algebraic_inverse() {
let a = AlgebraicNumber::from_rational(rat(4));
let inv_a = a.inverse().expect("test operation should succeed");
assert_eq!(
inv_a.approximate(),
BigRational::new(BigInt::from(1), BigInt::from(4))
);
let b = AlgebraicNumber::from_rational(rat(-2));
let inv_b = b.inverse().expect("test operation should succeed");
assert_eq!(
inv_b.approximate(),
BigRational::new(BigInt::from(-1), BigInt::from(2))
);
let c = AlgebraicNumber::from_rational(rat(0));
assert!(c.inverse().is_none());
}
#[test]
fn test_algebraic_div_rational() {
let a = AlgebraicNumber::from_rational(rat(10));
let b = a
.div_rational(&rat(2))
.expect("test operation should succeed");
assert_eq!(b.approximate(), rat(5));
let c = AlgebraicNumber::from_rational(rat(6));
let d = c
.div_rational(&rat(4))
.expect("test operation should succeed");
assert_eq!(
d.approximate(),
BigRational::new(BigInt::from(3), BigInt::from(2))
);
let e = AlgebraicNumber::from_rational(rat(5));
assert!(e.div_rational(&rat(0)).is_none());
}
#[test]
fn test_algebraic_pow() {
let a = AlgebraicNumber::from_rational(rat(2));
let b = a.pow(3).expect("test operation should succeed");
assert_eq!(b.approximate(), rat(8));
let c = AlgebraicNumber::from_rational(rat(3));
let d = c.pow(0).expect("test operation should succeed");
assert_eq!(d.approximate(), rat(1));
let e = AlgebraicNumber::from_rational(rat(2));
let f = e.pow(-1).expect("test operation should succeed");
assert_eq!(
f.approximate(),
BigRational::new(BigInt::from(1), BigInt::from(2))
);
let g = AlgebraicNumber::from_rational(rat(0));
assert!(g.pow(-1).is_none());
}
#[test]
fn test_algebraic_refine() {
let a = AlgebraicNumber::from_rational(rat(5));
let (lo1, hi1) = a.interval();
assert_eq!(lo1, hi1);
if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
let (lo1, hi1) = sqrt2.interval();
let width1 = hi1 - lo1;
sqrt2.refine();
let (lo2, hi2) = sqrt2.interval();
let width2 = hi2 - lo2;
assert!(width2 < width1);
}
}
#[test]
fn test_algebraic_approximate_with_precision() {
if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
let epsilon = BigRational::new(BigInt::from(1), BigInt::from(100));
let approx = sqrt2.approximate_with_precision(&epsilon);
let (lo, hi) = sqrt2.interval();
assert!(
hi - lo < epsilon,
"Interval width {} should be less than {}",
hi - lo,
epsilon
);
assert!(approx.is_positive(), "√2 approximation should be positive");
assert!(
approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
"√2 approximation should be less than 2"
);
}
}
#[test]
fn test_algebraic_add_algebraic() {
let mut a = AlgebraicNumber::from_rational(rat(2));
let mut b = AlgebraicNumber::from_rational(rat(3));
let c = a.add_algebraic(&mut b);
assert_eq!(c.approximate(), rat(5));
}
#[test]
fn test_algebraic_mul_algebraic() {
let mut a = AlgebraicNumber::from_rational(rat(2));
let mut b = AlgebraicNumber::from_rational(rat(3));
let c = a.mul_algebraic(&mut b);
assert_eq!(c.approximate(), rat(6));
}
#[test]
fn test_algebraic_add_irrational() {
let mut sqrt2_a = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
let mut sqrt2_b = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
let mut sum = sqrt2_a.add_algebraic(&mut sqrt2_b);
assert!(!sum.is_rational(), "√2 + √2 = 2√2 is irrational");
let x2_minus_8 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-8, &[])]);
let g = sum.polynomial().gcd_univariate(&x2_minus_8);
assert!(
g.degree(sum.var()) >= 1,
"sum's polynomial must share the root 2√2 (root of x²-8)"
);
for _ in 0..40 {
sum.refine();
}
let approx = sum.approximate();
assert!(
approx > rat(2) && approx < rat(3),
"2√2 ≈ 2.83, got {approx}"
);
}
#[test]
fn test_algebraic_mul_irrational() {
let mut sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
let mut sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
let mut product = sqrt2.mul_algebraic(&mut sqrt3);
assert!(!product.is_rational(), "√2 · √3 = √6 is irrational");
let x2_minus_6 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-6, &[])]);
let g = product.polynomial().gcd_univariate(&x2_minus_6);
assert!(
g.degree(product.var()) >= 1,
"product's polynomial must share the root √6 (root of x²-6)"
);
for _ in 0..40 {
product.refine();
}
let approx = product.approximate();
assert!(
approx > rat(2) && approx < rat(3),
"√6 ≈ 2.45, got {approx}"
);
}
#[test]
fn test_algebraic_add_mixed() {
let sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
let sum = sqrt2.add_rational(&rat(1));
assert!(sum.is_positive());
let approx = sum.approximate();
assert!(approx > rat(2), "1 + √2 should be > 2, got {}", approx);
}
#[test]
fn test_algebraic_mul_by_rational() {
let sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
assert!(!sqrt3.is_negative(), "√3 should not be negative");
let product = sqrt3.mul_rational(&rat(2));
let (lo, hi) = product.interval();
assert!(
!lo.is_negative(),
"Lower bound should be non-negative, got {}",
lo
);
assert!(
hi.is_positive(),
"Upper bound should be positive, got {}",
hi
);
let approx = product.approximate();
assert!(approx > rat(3), "2 * √3 should be > 3, got {}", approx);
}
}