use crate::polynomial::root_counting::Polynomial;
#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use core::fmt;
use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlgebraicNumberError {
NonIsolatingInterval,
NoRootInInterval,
ZeroPolynomial,
InvalidOperation(String),
}
impl fmt::Display for AlgebraicNumberError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonIsolatingInterval => write!(f, "Interval doesn't isolate a single root"),
Self::NoRootInInterval => write!(f, "No root in interval"),
Self::ZeroPolynomial => write!(f, "Polynomial is zero"),
Self::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg),
}
}
}
impl core::error::Error for AlgebraicNumberError {}
#[derive(Debug, Clone)]
pub struct AlgebraicNumber {
pub minimal_poly: Polynomial,
pub lower: BigRational,
pub upper: BigRational,
refinement_level: usize,
}
impl AlgebraicNumber {
pub fn new(
poly: Polynomial,
lower: BigRational,
upper: BigRational,
) -> Result<Self, AlgebraicNumberError> {
if poly.degree() == 0 && poly.coeffs.first().is_none_or(Zero::is_zero) {
return Err(AlgebraicNumberError::ZeroPolynomial);
}
if lower > upper {
return Err(AlgebraicNumberError::NonIsolatingInterval);
}
if lower == upper {
if poly.eval(&lower).is_zero() {
return Ok(Self {
minimal_poly: poly,
lower,
upper,
refinement_level: 0,
});
}
return Err(AlgebraicNumberError::NoRootInInterval);
}
let root_count = sturm_root_count(&poly, &lower, &upper);
if root_count != 1 {
return Err(AlgebraicNumberError::NonIsolatingInterval);
}
Ok(Self {
minimal_poly: poly,
lower,
upper,
refinement_level: 0,
})
}
pub fn from_rational(r: BigRational) -> Self {
let poly = Polynomial::new(vec![-r.clone(), BigRational::from(BigInt::from(1))]);
Self {
minimal_poly: poly,
lower: r.clone(),
upper: r,
refinement_level: 0,
}
}
pub fn is_rational(&self) -> bool {
self.minimal_poly.degree() == 1 || self.lower == self.upper
}
pub fn to_rational(&self) -> Option<BigRational> {
if self.is_rational() {
Some(self.lower.clone())
} else {
None
}
}
pub fn refine(&mut self) {
let mid = (&self.lower + &self.upper) / BigRational::from(BigInt::from(2));
let mid_value = self.minimal_poly.eval(&mid);
if mid_value.is_zero() {
self.lower = mid.clone();
self.upper = mid;
} else {
let lower_value = self.minimal_poly.eval(&self.lower);
if lower_value.signum() != mid_value.signum() {
self.upper = mid;
} else {
self.lower = mid;
}
}
self.refinement_level += 1;
}
pub fn refine_to_precision(&mut self, precision: &BigRational) {
while &self.upper - &self.lower > *precision {
self.refine();
}
}
pub fn interval_width(&self) -> BigRational {
&self.upper - &self.lower
}
pub fn midpoint(&self) -> BigRational {
(&self.lower + &self.upper) / BigRational::from(BigInt::from(2))
}
pub fn compare(&mut self, other: &mut AlgebraicNumber) -> Ordering {
loop {
if self.upper < other.lower {
return Ordering::Less;
}
if self.lower > other.upper {
return Ordering::Greater;
}
if self.minimal_poly == other.minimal_poly
&& self.lower == other.lower
&& self.upper == other.upper
{
return Ordering::Equal;
}
self.refine();
other.refine();
if self.refinement_level > 1000 {
return Ordering::Equal;
}
}
}
pub fn add(&self, other: &AlgebraicNumber) -> Result<AlgebraicNumber, AlgebraicNumberError> {
if self.is_rational() && other.is_rational() {
return Ok(AlgebraicNumber::from_rational(&self.lower + &other.lower));
}
let deg_p = self.minimal_poly.degree();
let deg_q = other.minimal_poly.degree();
let result_deg = deg_p * deg_q;
let num_points = result_deg + 1;
let eval_points: Vec<BigRational> = (0..num_points as i64)
.map(|k| BigRational::from(BigInt::from(k)))
.collect();
let values: Vec<BigRational> = eval_points
.iter()
.map(|x_val| {
let q_shifted = poly_substitute_affine_t(&other.minimal_poly, x_val);
univariate_resultant(&self.minimal_poly, &q_shifted)
})
.collect();
let raw_poly = lagrange_interpolate(&eval_points, &values);
if raw_poly.degree() == 0 && raw_poly.coeffs.first().is_none_or(Zero::is_zero) {
return Err(AlgebraicNumberError::ZeroPolynomial);
}
let result_poly = square_free_part(raw_poly);
let sum_lower = &self.lower + &other.lower;
let sum_upper = &self.upper + &other.upper;
isolate_root_in_interval(result_poly, sum_lower, sum_upper)
}
pub fn mul(&self, other: &AlgebraicNumber) -> Result<AlgebraicNumber, AlgebraicNumberError> {
if self.is_rational() && other.is_rational() {
return Ok(AlgebraicNumber::from_rational(&self.lower * &other.lower));
}
let deg_p = self.minimal_poly.degree();
let deg_q = other.minimal_poly.degree();
let result_deg = deg_p * deg_q;
let num_points = result_deg + 1;
let eval_points: Vec<BigRational> = (1..=(num_points as i64))
.map(|k| BigRational::from(BigInt::from(k)))
.collect();
let values: Vec<BigRational> = eval_points
.iter()
.map(|x_val| {
let h = poly_mul_pow_quot(&other.minimal_poly, x_val, deg_q);
univariate_resultant(&self.minimal_poly, &h)
})
.collect();
let raw_poly = lagrange_interpolate(&eval_points, &values);
if raw_poly.degree() == 0 && raw_poly.coeffs.first().is_none_or(Zero::is_zero) {
return Err(AlgebraicNumberError::ZeroPolynomial);
}
let result_poly = square_free_part(raw_poly);
let corners = [
&self.lower * &other.lower,
&self.lower * &other.upper,
&self.upper * &other.lower,
&self.upper * &other.upper,
];
let prod_lower = corners
.iter()
.min_by(|a, b| a.cmp(b))
.expect("4 elements")
.clone();
let prod_upper = corners
.iter()
.max_by(|a, b| a.cmp(b))
.expect("4 elements")
.clone();
isolate_root_in_interval(result_poly, prod_lower, prod_upper)
}
pub fn negate(&self) -> AlgebraicNumber {
let neg_poly = poly_substitute_neg_x(&self.minimal_poly);
AlgebraicNumber {
minimal_poly: neg_poly,
lower: -&self.upper,
upper: -&self.lower,
refinement_level: self.refinement_level,
}
}
pub fn signum(&self) -> i32 {
if self.upper < BigRational::zero() {
-1
} else if self.lower > BigRational::zero() {
1
} else if self.lower == self.upper && self.lower.is_zero() {
0
} else {
let mut copy = self.clone();
copy.refine();
copy.signum()
}
}
}
fn poly_substitute_neg_x(poly: &Polynomial) -> Polynomial {
let coeffs: Vec<BigRational> = poly
.coeffs
.iter()
.enumerate()
.map(|(i, c)| if i % 2 == 0 { c.clone() } else { -c })
.collect();
Polynomial::new(coeffs)
}
fn sturm_root_count(poly: &Polynomial, lower: &BigRational, upper: &BigRational) -> usize {
let seq = build_sturm_sequence(poly);
let v_lower = sign_variations(&seq, lower);
let v_upper = sign_variations(&seq, upper);
(v_lower as isize - v_upper as isize).unsigned_abs()
}
fn build_sturm_sequence(poly: &Polynomial) -> Vec<Polynomial> {
let mut seq = vec![poly.clone(), poly.derivative()];
loop {
let n = seq.len();
let last = &seq[n - 1];
if last.degree() == 0 {
break;
}
let remainder = seq[n - 2].remainder(last);
let negated = Polynomial::new(remainder.coeffs.iter().map(|c| -c).collect());
if negated.degree() == 0 && negated.coeffs.first().is_none_or(Zero::is_zero) {
break;
}
seq.push(negated);
if seq.len() > 1000 {
break;
}
}
seq
}
fn sign_variations(seq: &[Polynomial], point: &BigRational) -> usize {
let signs: Vec<i32> = seq
.iter()
.map(|p| {
let val = p.eval(point);
if val.is_positive() {
1
} else if val.is_negative() {
-1
} else {
0
}
})
.filter(|&s| s != 0)
.collect();
signs.windows(2).filter(|w| w[0] != w[1]).count()
}
fn square_free_part(p: Polynomial) -> Polynomial {
let deriv = p.derivative();
if deriv.degree() == 0 && deriv.coeffs.first().is_none_or(|c| c.is_zero()) {
return p;
}
let g = univariate_gcd(&p, &deriv);
if g.degree() == 0 {
return p;
}
exact_poly_div(&p, &g)
}
fn univariate_gcd(p: &Polynomial, q: &Polynomial) -> Polynomial {
let mut a = p.clone();
let mut b = q.clone();
loop {
let b_is_zero = b.coeffs.iter().all(|c| c.is_zero());
if b_is_zero {
return make_monic(a);
}
let r = a.remainder(&b);
a = b;
b = r;
}
}
fn exact_poly_div(a: &Polynomial, b: &Polynomial) -> Polynomial {
let deg_b = b.degree();
let lead_b = b.coeffs[deg_b].clone();
if a.degree() < deg_b {
return Polynomial::new(vec![BigRational::zero()]);
}
let result_deg = a.degree() - deg_b;
let mut quotient_coeffs = vec![BigRational::zero(); result_deg + 1];
let mut rem_coeffs = a.coeffs.clone();
let mut rem_deg = a.degree();
while rem_deg >= deg_b {
if rem_coeffs[rem_deg].is_zero() {
if rem_deg == 0 {
break;
}
rem_deg -= 1;
continue;
}
let factor = rem_coeffs[rem_deg].clone() / &lead_b;
let shift = rem_deg - deg_b;
quotient_coeffs[shift] = factor.clone();
for i in 0..=deg_b {
let upd = rem_coeffs[shift + i].clone() - &factor * &b.coeffs[i];
rem_coeffs[shift + i] = upd;
}
if rem_deg == 0 {
break;
}
rem_deg -= 1;
}
Polynomial::new(quotient_coeffs)
}
fn make_monic(p: Polynomial) -> Polynomial {
let deg = p.degree();
let lead = &p.coeffs[deg];
if lead.is_zero() {
return p;
}
let inv = lead.clone().recip();
let coeffs: Vec<BigRational> = p.coeffs.iter().map(|c| c * &inv).collect();
Polynomial::new(coeffs)
}
fn poly_substitute_affine_t(q: &Polynomial, k: &BigRational) -> Polynomial {
let n = q.coeffs.len();
if n == 0 {
return Polynomial::new(vec![BigRational::zero()]);
}
let mut result_coeffs = vec![BigRational::zero(); n];
for (i, q_i) in q.coeffs.iter().enumerate() {
if q_i.is_zero() {
continue;
}
let binom_row = binomial_row(i);
for (j, binom_ij) in binom_row.iter().enumerate() {
let k_pow = rational_pow(k, i - j);
let sign = if j % 2 == 0 {
BigRational::one()
} else {
-BigRational::one()
};
let contrib = q_i * binom_ij * k_pow * sign;
result_coeffs[j] = result_coeffs[j].clone() + contrib;
}
}
Polynomial::new(result_coeffs)
}
fn poly_mul_pow_quot(q: &Polynomial, x_val: &BigRational, m: usize) -> Polynomial {
let mut result_coeffs = vec![BigRational::zero(); m + 1];
for (i, q_i) in q.coeffs.iter().enumerate() {
if q_i.is_zero() {
continue;
}
let j = m.saturating_sub(i);
let x_pow = rational_pow(x_val, i);
result_coeffs[j] = result_coeffs[j].clone() + q_i * x_pow;
}
Polynomial::new(result_coeffs)
}
fn univariate_resultant(p: &Polynomial, q: &Polynomial) -> BigRational {
let m = p.degree();
let n = q.degree();
if m == 0 {
let c = p.coeffs.first().cloned().unwrap_or_else(BigRational::zero);
return rational_pow(&c, n);
}
if n == 0 {
let c = q.coeffs.first().cloned().unwrap_or_else(BigRational::zero);
return rational_pow(&c, m);
}
let p_rev: Vec<BigRational> = p.coeffs.iter().rev().cloned().collect();
let q_rev: Vec<BigRational> = q.coeffs.iter().rev().cloned().collect();
let size = m + n;
let mut mat: Vec<Vec<BigRational>> = vec![vec![BigRational::zero(); size]; size];
for r in 0..n {
mat[r][r..(m + r + 1)].clone_from_slice(&p_rev[..(m + 1)]);
}
for r in 0..m {
mat[n + r][r..(n + r + 1)].clone_from_slice(&q_rev[..(n + 1)]);
}
bareiss_rat_det(mat)
}
fn bareiss_rat_det(mut mat: Vec<Vec<BigRational>>) -> BigRational {
let n = mat.len();
if n == 0 {
return BigRational::one();
}
if n == 1 {
return mat.remove(0).remove(0);
}
let mut sign = BigRational::one();
let neg_one = -BigRational::one();
for col in 0..n {
let pivot_row = (col..n).find(|&r| !mat[r][col].is_zero());
let pivot_row = match pivot_row {
Some(r) => r,
None => return BigRational::zero(),
};
if pivot_row != col {
mat.swap(col, pivot_row);
sign *= &neg_one;
}
let pivot = mat[col][col].clone();
for row in (col + 1)..n {
for j in (col + 1)..n {
let prod1 = &pivot * &mat[row][j];
let prod2 = &mat[row][col] * &mat[col][j];
let diff = prod1 - prod2;
if col == 0 {
mat[row][j] = diff;
} else {
let prev_pivot = mat[col - 1][col - 1].clone();
if prev_pivot.is_zero() {
mat[row][j] = BigRational::zero();
} else {
mat[row][j] = diff / prev_pivot;
}
}
}
mat[row][col] = BigRational::zero();
}
}
sign * mat[n - 1][n - 1].clone()
}
fn lagrange_interpolate(points: &[BigRational], values: &[BigRational]) -> Polynomial {
let n = points.len();
assert_eq!(n, values.len(), "points and values must have equal length");
if n == 0 {
return Polynomial::new(vec![BigRational::zero()]);
}
if n == 1 {
return Polynomial::new(vec![values[0].clone()]);
}
let mut result_coeffs = vec![BigRational::zero(); n];
for i in 0..n {
let mut basis_coeffs = vec![BigRational::one()]; let mut denom = BigRational::one();
for j in 0..n {
if j == i {
continue;
}
let mut new_coeffs = vec![BigRational::zero(); basis_coeffs.len() + 1];
for (k, c) in basis_coeffs.iter().enumerate() {
new_coeffs[k + 1] = new_coeffs[k + 1].clone() + c;
new_coeffs[k] = new_coeffs[k].clone() - c * &points[j];
}
basis_coeffs = new_coeffs;
denom *= &points[i] - &points[j];
}
let scale = &values[i] / &denom;
for (k, c) in basis_coeffs.iter().enumerate() {
result_coeffs[k] = result_coeffs[k].clone() + c * &scale;
}
}
Polynomial::new(result_coeffs)
}
fn binomial_row(k: usize) -> Vec<BigRational> {
let mut row = vec![BigRational::one(); k + 1];
for i in 1..k {
for j in (1..i + 1).rev() {
let prev = row[j - 1].clone();
row[j] = row[j].clone() + prev;
}
}
row
}
fn rational_pow(base: &BigRational, exp: usize) -> BigRational {
if exp == 0 {
return BigRational::one();
}
let mut result = BigRational::one();
let mut base = base.clone();
let mut exp = exp;
while exp > 0 {
if exp % 2 == 1 {
result *= &base;
}
let b = base.clone();
base *= &b;
exp /= 2;
}
result
}
fn isolate_root_in_interval(
r: Polynomial,
lo: BigRational,
hi: BigRational,
) -> Result<AlgebraicNumber, AlgebraicNumberError> {
let mut lo = lo;
let mut hi = hi;
if lo == hi {
if r.eval(&lo).is_zero() {
return AlgebraicNumber::new(r, lo.clone(), hi);
}
return Err(AlgebraicNumberError::NoRootInInterval);
}
let eps = BigRational::new(BigInt::from(1), BigInt::from(1_000_000_000_i64));
lo -= &eps;
hi += &eps;
for _ in 0..200 {
let count = sturm_root_count(&r, &lo, &hi);
if count == 1 {
return AlgebraicNumber::new(r, lo, hi);
}
if count == 0 {
return Err(AlgebraicNumberError::NoRootInInterval);
}
let mid = (&lo + &hi) / BigRational::from(BigInt::from(2));
let count_left = sturm_root_count(&r, &lo, &mid);
if count_left >= 1 {
hi = mid;
} else {
lo = mid;
}
}
Err(AlgebraicNumberError::NonIsolatingInterval)
}
impl PartialEq for AlgebraicNumber {
fn eq(&self, other: &Self) -> bool {
let mut a = self.clone();
let mut b = other.clone();
matches!(a.compare(&mut b), Ordering::Equal)
}
}
impl Eq for AlgebraicNumber {}
impl PartialOrd for AlgebraicNumber {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AlgebraicNumber {
fn cmp(&self, other: &Self) -> Ordering {
let mut a = self.clone();
let mut b = other.clone();
a.compare(&mut b)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64) -> BigRational {
BigRational::from(BigInt::from(n))
}
#[test]
fn test_from_rational() {
let alg = AlgebraicNumber::from_rational(rat(42));
assert!(alg.is_rational());
assert_eq!(alg.to_rational(), Some(rat(42)));
}
#[test]
fn test_rational_arithmetic() {
let a = AlgebraicNumber::from_rational(rat(3));
let b = AlgebraicNumber::from_rational(rat(5));
let sum = a.add(&b).expect("test operation should succeed");
assert!(sum.is_rational());
assert_eq!(sum.to_rational(), Some(rat(8)));
let prod = a.mul(&b).expect("test operation should succeed");
assert!(prod.is_rational());
assert_eq!(prod.to_rational(), Some(rat(15)));
}
#[test]
fn test_negate() {
let a = AlgebraicNumber::from_rational(rat(5));
let neg_a = a.negate();
assert!(neg_a.is_rational());
assert_eq!(neg_a.to_rational(), Some(rat(-5)));
}
#[test]
fn test_compare() {
let a = AlgebraicNumber::from_rational(rat(3));
let b = AlgebraicNumber::from_rational(rat(5));
assert!(a < b);
assert!(b > a);
assert_eq!(a, a.clone());
}
#[test]
fn test_signum() {
let pos = AlgebraicNumber::from_rational(rat(5));
let neg = AlgebraicNumber::from_rational(rat(-3));
let zero = AlgebraicNumber::from_rational(rat(0));
assert_eq!(pos.signum(), 1);
assert_eq!(neg.signum(), -1);
assert_eq!(zero.signum(), 0);
}
#[test]
fn test_refine() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let mut alg =
AlgebraicNumber::new(poly, rat(1), rat(2)).expect("test operation should succeed");
let initial_width = alg.interval_width();
alg.refine();
let refined_width = alg.interval_width();
assert!(refined_width < initial_width);
}
#[test]
fn test_interval_width() {
let alg = AlgebraicNumber::from_rational(rat(5));
assert_eq!(alg.interval_width(), rat(0));
}
#[test]
fn test_new_rejects_non_isolating_interval() {
let poly = Polynomial::new(vec![rat(-1), rat(0), rat(1)]);
let result = AlgebraicNumber::new(poly, rat(-2), rat(2));
assert!(result.is_err());
}
#[test]
fn test_new_accepts_isolating_interval() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let result = AlgebraicNumber::new(poly, rat(1), rat(2));
assert!(result.is_ok());
}
#[test]
fn test_sturm_root_count() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let count = sturm_root_count(&poly, &rat(1), &rat(2));
assert_eq!(count, 1);
}
#[test]
fn test_poly_substitute_neg_x() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let neg = poly_substitute_neg_x(&poly);
assert_eq!(neg.eval(&rat(2)), poly.eval(&rat(2)));
let poly2 = Polynomial::new(vec![rat(0), rat(0), rat(0), rat(1)]);
let neg2 = poly_substitute_neg_x(&poly2);
assert_eq!(neg2.eval(&rat(2)), -poly2.eval(&rat(2)));
}
fn sqrt2() -> AlgebraicNumber {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
AlgebraicNumber::new(poly, rat(1), rat(2)).expect("√2 construction")
}
fn sqrt3() -> AlgebraicNumber {
let poly = Polynomial::new(vec![rat(-3), rat(0), rat(1)]);
AlgebraicNumber::new(poly, rat(1), rat(2)).expect("√3 construction")
}
#[test]
fn test_add_irrational_sum_polynomial_root() {
let a = sqrt2();
let b = sqrt3();
let mut sum = a.add(&b).expect("√2 + √3 should succeed");
let p = |x: &num_rational::BigRational| {
x.clone() * x.clone() * x.clone() * x.clone() - rat(10) * x.clone() * x.clone() + rat(1)
};
let tight = num_rational::BigRational::new(BigInt::from(1), BigInt::from(1 << 20));
sum.refine_to_precision(&tight);
let val_lo = p(&sum.lower);
let val_hi = p(&sum.upper);
assert!(
val_lo.is_negative() != val_hi.is_negative() || val_lo.is_zero() || val_hi.is_zero(),
"expected sign change: p(lo)={:?}, p(hi)={:?}",
val_lo,
val_hi
);
assert!(
sum.lower < rat(4) && sum.upper > rat(3),
"interval [{:?}, {:?}] should contain 3.146",
sum.lower,
sum.upper
);
}
#[test]
fn test_mul_irrational_product_polynomial_root() {
let a = sqrt2();
let b = sqrt3();
let mut prod = a.mul(&b).expect("√2 * √3 should succeed");
assert!(
prod.lower < num_rational::BigRational::new(BigInt::from(3), BigInt::from(1)),
"lower bound should be < 3"
);
assert!(
prod.upper > num_rational::BigRational::new(BigInt::from(2), BigInt::from(1)),
"upper bound should be > 2"
);
let p = |x: &num_rational::BigRational| x.clone() * x.clone() - rat(6);
let tight = num_rational::BigRational::new(BigInt::from(1), BigInt::from(1 << 20));
prod.refine_to_precision(&tight);
let val_lo = p(&prod.lower);
let val_hi = p(&prod.upper);
assert!(
val_lo.is_negative() != val_hi.is_negative() || val_lo.is_zero() || val_hi.is_zero(),
"expected sign change for √6: p(lo)={:?}, p(hi)={:?}",
val_lo,
val_hi
);
}
#[test]
fn test_add_rational_plus_irrational() {
let one = AlgebraicNumber::from_rational(rat(1));
let s2 = sqrt2();
let result = one.add(&s2).expect("1 + √2 should succeed");
assert!(
result.lower < rat(3) && result.upper > rat(2),
"1 + √2 expected in (2,3), got [{:?}, {:?}]",
result.lower,
result.upper
);
}
#[test]
fn test_poly_substitute_affine_t() {
let q = Polynomial::new(vec![rat(-1), rat(1)]);
let shifted = poly_substitute_affine_t(&q, &rat(3));
assert_eq!(shifted.eval(&rat(0)), rat(2), "q(3-0) = 2");
assert_eq!(shifted.eval(&rat(1)), rat(1), "q(3-1) = 2 - 1 = 1");
}
#[test]
fn test_univariate_resultant_linear() {
let p = Polynomial::new(vec![rat(-3), rat(1)]); let q = Polynomial::new(vec![rat(-5), rat(1)]); let res = univariate_resultant(&p, &q);
assert!(
!res.is_zero(),
"resultant of coprime linear polys should be nonzero"
);
}
#[test]
fn test_lagrange_interpolate_linear() {
use num_bigint::BigInt;
let points = vec![
num_rational::BigRational::from(BigInt::from(0)),
num_rational::BigRational::from(BigInt::from(1)),
];
let values = vec![
num_rational::BigRational::from(BigInt::from(3)),
num_rational::BigRational::from(BigInt::from(5)),
];
let poly = lagrange_interpolate(&points, &values);
assert_eq!(poly.eval(&points[0]), values[0]);
assert_eq!(poly.eval(&points[1]), values[1]);
}
}