1use crate::polynomial::{Polynomial, Var};
10#[allow(unused_imports)]
11use crate::prelude::*;
12use core::cmp::Ordering;
13use num_bigint::BigInt;
14use num_integer::Integer;
15use num_rational::BigRational;
16use num_traits::{One, Signed, Zero};
17
18#[derive(Clone, Debug)]
26pub struct AlgebraicNumber {
27 polynomial: Polynomial,
30
31 var: Var,
33
34 lower: BigRational,
36
37 upper: BigRational,
39}
40
41impl AlgebraicNumber {
42 pub fn new(polynomial: Polynomial, var: Var, lower: BigRational, upper: BigRational) -> Self {
47 let num_roots = polynomial.count_roots_in_interval(var, &lower, &upper);
49 assert_eq!(
50 num_roots, 1,
51 "Interval must contain exactly one root, found {}",
52 num_roots
53 );
54
55 Self {
56 polynomial: polynomial.primitive(),
57 var,
58 lower,
59 upper,
60 }
61 }
62
63 pub fn from_rational(r: BigRational) -> Self {
65 let poly = Polynomial::from_var(0).sub(&Polynomial::constant(r.clone()));
67
68 Self {
69 polynomial: poly,
70 var: 0,
71 lower: r.clone(),
72 upper: r,
73 }
74 }
75
76 pub fn sqrt(n: &BigRational) -> Option<Self> {
80 if n.is_negative() {
81 return None;
82 }
83
84 if n.is_zero() {
85 return Some(Self::from_rational(BigRational::zero()));
86 }
87
88 if let Some(sqrt_n) = crate::polynomial::rational_sqrt(n) {
90 return Some(Self::from_rational(sqrt_n));
91 }
92
93 let poly =
95 Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]).sub(&Polynomial::constant(n.clone()));
96
97 let roots = poly.isolate_roots(0);
99
100 for (lo, hi) in roots {
102 let mid = (&lo + &hi) / BigRational::from_integer(BigInt::from(2));
103
104 if mid.is_positive() {
106 let adjusted_lo = if lo.is_negative() {
109 BigRational::zero()
110 } else {
111 lo
112 };
113
114 if poly.count_roots_in_interval(0, &adjusted_lo, &hi) == 1 {
116 return Some(Self::new(poly.clone(), 0, adjusted_lo, hi));
117 }
118 }
119 }
120
121 None
122 }
123
124 pub fn polynomial(&self) -> &Polynomial {
126 &self.polynomial
127 }
128
129 pub fn interval(&self) -> (&BigRational, &BigRational) {
131 (&self.lower, &self.upper)
132 }
133
134 pub fn var(&self) -> Var {
136 self.var
137 }
138
139 pub fn refine(&mut self) {
143 let mid = (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2));
144
145 let val_mid = self.polynomial.eval_at(self.var, &mid);
146
147 if val_mid.constant_term().is_zero() {
148 self.lower = mid.clone();
150 self.upper = mid;
151 } else {
152 let val_lo = self.polynomial.eval_at(self.var, &self.lower);
154 let val_mid = self.polynomial.eval_at(self.var, &mid);
155
156 let sign_lo = val_lo.constant_term().signum();
157 let sign_mid = val_mid.constant_term().signum();
158
159 if sign_lo != sign_mid {
160 self.upper = mid;
162 } else {
163 self.lower = mid;
165 }
166 }
167 }
168
169 pub fn approximate(&self) -> BigRational {
173 (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2))
174 }
175
176 pub fn approximate_with_precision(&mut self, epsilon: &BigRational) -> BigRational {
180 while &self.upper - &self.lower > *epsilon {
181 self.refine();
182 }
183 self.approximate()
184 }
185
186 pub fn is_zero(&self) -> bool {
188 self.lower.is_zero() && self.upper.is_zero()
189 }
190
191 pub fn is_positive(&self) -> bool {
193 self.lower.is_positive()
194 }
195
196 pub fn is_negative(&self) -> bool {
198 self.upper.is_negative()
199 }
200
201 pub fn is_rational(&self) -> bool {
206 let degree = self.polynomial.degree(self.var);
209 degree <= 1 || self.lower == self.upper
210 }
211
212 pub fn sign(&self) -> Option<i8> {
220 if self.is_zero() {
221 Some(0)
222 } else if self.is_positive() {
223 Some(1)
224 } else if self.is_negative() {
225 Some(-1)
226 } else {
227 None
228 }
229 }
230
231 pub fn cmp_algebraic(&mut self, other: &mut AlgebraicNumber) -> Ordering {
233 let max_iterations = 1000;
235 let mut iterations = 0;
236
237 while iterations < max_iterations {
238 iterations += 1;
239
240 if self.upper < other.lower {
242 return Ordering::Less;
243 }
244 if self.lower > other.upper {
245 return Ordering::Greater;
246 }
247 if self.lower == self.upper && other.lower == other.upper && self.lower == other.lower {
248 return Ordering::Equal;
249 }
250
251 self.refine();
253 other.refine();
254 }
255
256 self.approximate().cmp(&other.approximate())
259 }
260
261 pub fn cmp_rational(&mut self, r: &BigRational) -> Ordering {
263 let max_iterations = 1000;
265 let mut iterations = 0;
266
267 while iterations < max_iterations {
268 iterations += 1;
269
270 if &self.upper < r {
271 return Ordering::Less;
272 }
273 if &self.lower > r {
274 return Ordering::Greater;
275 }
276 if &self.lower == r && &self.upper == r {
277 return Ordering::Equal;
278 }
279
280 self.refine();
281 }
282
283 self.approximate().cmp(r)
285 }
286
287 pub fn negate(&self) -> AlgebraicNumber {
291 let negated_poly = negate_polynomial(&self.polynomial, self.var);
293
294 AlgebraicNumber {
295 polynomial: negated_poly,
296 var: self.var,
297 lower: -self.upper.clone(),
298 upper: -self.lower.clone(),
299 }
300 }
301
302 pub fn add_rational(&self, r: &BigRational) -> AlgebraicNumber {
304 let shifted_poly = self.polynomial.substitute(
306 self.var,
307 &Polynomial::from_var(self.var).sub(&Polynomial::constant(r.clone())),
308 );
309
310 AlgebraicNumber {
311 polynomial: shifted_poly,
312 var: self.var,
313 lower: &self.lower + r,
314 upper: &self.upper + r,
315 }
316 }
317
318 pub fn mul_rational(&self, r: &BigRational) -> AlgebraicNumber {
320 if r.is_zero() {
321 return Self::from_rational(BigRational::zero());
322 }
323
324 let scaled_poly = scale_polynomial_var(&self.polynomial, self.var, r);
327
328 let (new_lower, new_upper) = if r.is_positive() {
329 (&self.lower * r, &self.upper * r)
330 } else {
331 (&self.upper * r, &self.lower * r)
332 };
333
334 AlgebraicNumber {
335 polynomial: scaled_poly,
336 var: self.var,
337 lower: new_lower,
338 upper: new_upper,
339 }
340 }
341
342 pub fn sub_rational(&self, r: &BigRational) -> AlgebraicNumber {
344 self.add_rational(&(-r))
346 }
347
348 pub fn inverse(&self) -> Option<AlgebraicNumber> {
352 if self.is_zero() {
353 return None;
354 }
355
356 let inv_poly = reciprocal_polynomial(&self.polynomial, self.var);
359
360 let (new_lower, new_upper) = if self.is_positive() || self.is_negative() {
364 (
365 BigRational::from_integer(BigInt::from(1)) / &self.upper,
366 BigRational::from_integer(BigInt::from(1)) / &self.lower,
367 )
368 } else {
369 return None;
371 };
372
373 Some(AlgebraicNumber {
374 polynomial: inv_poly,
375 var: self.var,
376 lower: new_lower,
377 upper: new_upper,
378 })
379 }
380
381 pub fn div_rational(&self, r: &BigRational) -> Option<AlgebraicNumber> {
385 if r.is_zero() {
386 return None;
387 }
388
389 Some(self.mul_rational(&(BigRational::from_integer(BigInt::from(1)) / r)))
391 }
392
393 pub fn pow(&self, n: i32) -> Option<AlgebraicNumber> {
395 if n == 0 {
396 return Some(Self::from_rational(BigRational::from_integer(
397 BigInt::from(1),
398 )));
399 }
400
401 if n < 0 {
402 return self.inverse()?.pow(-n);
404 }
405
406 let mut result = Self::from_rational(BigRational::from_integer(BigInt::from(1)));
408 let mut base = self.clone();
409 let mut exp = n as u32;
410
411 while exp > 0 {
412 if exp % 2 == 1 {
413 let approx = base.approximate() * result.approximate();
416 result = Self::from_rational(approx);
417 }
418 if exp > 1 {
419 let approx = base.approximate() * base.approximate();
421 base = Self::from_rational(approx);
422 }
423 exp /= 2;
424 }
425
426 Some(result)
427 }
428
429 pub fn add_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
448 if self.is_rational() {
450 return other.add_rational(&self.approximate());
451 }
452 if other.is_rational() {
453 return self.add_rational(&other.approximate());
454 }
455
456 let p_y = self
460 .polynomial
461 .substitute(self.var, &Polynomial::from_var(Y_VAR));
462 let z_minus_y = Polynomial::from_var(Z_VAR).sub(&Polynomial::from_var(Y_VAR));
463 let q_shift = other.polynomial.substitute(other.var, &z_minus_y);
464
465 let r = p_y.resultant(&q_shift, Y_VAR).square_free();
467
468 combine_via_resultant(self, other, r, Z_VAR, false)
469 }
470
471 pub fn mul_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
486 if self.is_rational() {
487 return other.mul_rational(&self.approximate());
488 }
489 if other.is_rational() {
490 return self.mul_rational(&other.approximate());
491 }
492 if self.is_zero() || other.is_zero() {
495 return Self::from_rational(BigRational::zero());
496 }
497
498 let p_y = self
499 .polynomial
500 .substitute(self.var, &Polynomial::from_var(Y_VAR));
501 let q_star = scale_for_product(&other.polynomial, other.var, Z_VAR);
504
505 let r = p_y.resultant(&q_star, Y_VAR).square_free();
507
508 combine_via_resultant(self, other, r, Z_VAR, true)
509 }
510}
511
512const Z_VAR: Var = 0;
516const Y_VAR: Var = 1;
518
519fn combine_via_resultant(
538 a: &mut AlgebraicNumber,
539 b: &mut AlgebraicNumber,
540 r_poly: Polynomial,
541 z_var: Var,
542 is_product: bool,
543) -> AlgebraicNumber {
544 for _ in 0..300 {
545 let (lo, hi) = combined_interval(a, b, is_product);
546
547 let lo_is_root = r_poly.eval_at(z_var, &lo).constant_term().is_zero();
550 let hi_is_root = r_poly.eval_at(z_var, &hi).constant_term().is_zero();
551
552 if !lo_is_root && !hi_is_root && r_poly.count_roots_in_interval(z_var, &lo, &hi) == 1 {
553 if let Some(root) = rational_root_in(&r_poly, z_var, &lo, &hi) {
556 return AlgebraicNumber::from_rational(root);
557 }
558 return AlgebraicNumber::new(r_poly, z_var, lo, hi);
559 }
560
561 a.refine();
562 b.refine();
563 }
564
565 AlgebraicNumber::from_rational(combined_point(a, b, is_product))
569}
570
571fn combined_interval(
574 a: &AlgebraicNumber,
575 b: &AlgebraicNumber,
576 is_product: bool,
577) -> (BigRational, BigRational) {
578 if is_product {
579 product_bounds(&a.lower, &a.upper, &b.lower, &b.upper)
580 } else {
581 (&a.lower + &b.lower, &a.upper + &b.upper)
582 }
583}
584
585fn combined_point(a: &AlgebraicNumber, b: &AlgebraicNumber, is_product: bool) -> BigRational {
588 if is_product {
589 a.approximate() * b.approximate()
590 } else {
591 a.approximate() + b.approximate()
592 }
593}
594
595fn product_bounds(
598 a_lo: &BigRational,
599 a_hi: &BigRational,
600 b_lo: &BigRational,
601 b_hi: &BigRational,
602) -> (BigRational, BigRational) {
603 let corners = [a_lo * b_lo, a_lo * b_hi, a_hi * b_lo, a_hi * b_hi];
604 let mut lo = corners[0].clone();
605 let mut hi = corners[0].clone();
606 for c in &corners[1..] {
607 if *c < lo {
608 lo = c.clone();
609 }
610 if *c > hi {
611 hi = c.clone();
612 }
613 }
614 (lo, hi)
615}
616
617fn rational_root_in(
631 poly: &Polynomial,
632 var: Var,
633 lo: &BigRational,
634 hi: &BigRational,
635) -> Option<BigRational> {
636 let deg = poly.degree(var);
637 if deg == 0 {
638 return None;
639 }
640
641 let coeffs: Vec<BigRational> = (0..=deg).map(|k| poly.univ_coeff(var, k)).collect();
643 let mut den_lcm = BigInt::one();
644 for c in &coeffs {
645 den_lcm = den_lcm.lcm(c.denom());
646 }
647 let int_coeffs: Vec<BigInt> = coeffs
648 .iter()
649 .map(|c| c.numer() * (&den_lcm / c.denom()))
650 .collect();
651
652 let a0 = &int_coeffs[0];
653 let an = &int_coeffs[deg as usize];
654
655 if a0.is_zero() {
660 let zero = BigRational::zero();
661 if lo < &zero && &zero < hi {
662 return Some(zero);
663 }
664 return None;
665 }
666
667 let a0_i64 = i64::try_from(a0.abs()).ok().filter(|&x| x <= 1_000_000)?;
669 let an_i64 = i64::try_from(an.abs()).ok().filter(|&x| x <= 1_000_000)?;
670
671 let num_divs = divisors_i64(a0_i64);
672 let den_divs = divisors_i64(an_i64);
673
674 for &p in &num_divs {
675 for &q in &den_divs {
676 for sign in [1i64, -1i64] {
677 let cand = BigRational::new(BigInt::from(sign * p), BigInt::from(q));
678 if &cand <= lo || &cand >= hi {
679 continue;
680 }
681 if poly.eval_at(var, &cand).constant_term().is_zero() {
682 return Some(cand);
683 }
684 }
685 }
686 }
687 None
688}
689
690fn divisors_i64(n: i64) -> Vec<i64> {
692 let n = n.abs();
693 let mut divs = Vec::new();
694 let mut d = 1i64;
695 while d.saturating_mul(d) <= n {
696 if n % d == 0 {
697 divs.push(d);
698 if d != n / d {
699 divs.push(n / d);
700 }
701 }
702 d += 1;
703 }
704 divs
705}
706
707fn negate_polynomial(p: &Polynomial, var: Var) -> Polynomial {
709 let terms: Vec<_> = p
710 .terms()
711 .iter()
712 .map(|term| {
713 let degree = term.monomial.degree(var);
714 let coeff = if degree % 2 == 1 {
715 -term.coeff.clone()
716 } else {
717 term.coeff.clone()
718 };
719 crate::polynomial::Term::new(coeff, term.monomial.clone())
720 })
721 .collect();
722
723 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
724}
725
726fn scale_polynomial_var(p: &Polynomial, var: Var, r: &BigRational) -> Polynomial {
728 if r.is_zero() {
729 return Polynomial::zero();
730 }
731
732 let terms: Vec<_> = p
733 .terms()
734 .iter()
735 .map(|term| {
736 let degree = term.monomial.degree(var);
737 let new_coeff = &term.coeff / r.pow(degree as i32);
739 crate::polynomial::Term::new(new_coeff, term.monomial.clone())
740 })
741 .collect();
742
743 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
744}
745
746fn reciprocal_polynomial(p: &Polynomial, var: Var) -> Polynomial {
750 let max_degree = p
752 .terms()
753 .iter()
754 .map(|term| term.monomial.degree(var))
755 .max()
756 .unwrap_or(0);
757
758 let terms: Vec<_> = p
759 .terms()
760 .iter()
761 .map(|term| {
762 let degree = term.monomial.degree(var);
763 let new_powers: Vec<(Var, u32)> = term
767 .monomial
768 .vars()
769 .iter()
770 .map(|vp| {
771 if vp.var == var {
772 (vp.var, max_degree - degree)
773 } else {
774 (vp.var, vp.power)
775 }
776 })
777 .collect();
778
779 let new_monomial =
780 if new_powers.is_empty() || (new_powers.len() == 1 && new_powers[0].1 == 0) {
781 crate::polynomial::Monomial::unit()
782 } else {
783 crate::polynomial::Monomial::from_powers(new_powers)
784 };
785
786 crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
787 })
788 .collect();
789
790 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
791}
792
793#[allow(dead_code)]
797fn shift_var(p: &Polynomial, old_var: Var, new_var: Var) -> Polynomial {
798 if old_var == new_var {
799 return p.clone();
800 }
801
802 let terms: Vec<_> = p
803 .terms()
804 .iter()
805 .map(|term| {
806 let new_powers: Vec<(Var, u32)> = term
807 .monomial
808 .vars()
809 .iter()
810 .map(|vp| {
811 if vp.var == old_var {
812 (new_var, vp.power)
813 } else {
814 (vp.var, vp.power)
815 }
816 })
817 .collect();
818
819 let new_monomial = if new_powers.is_empty() {
820 crate::polynomial::Monomial::unit()
821 } else {
822 crate::polynomial::Monomial::from_powers(new_powers)
823 };
824
825 crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
826 })
827 .collect();
828
829 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
830}
831
832fn scale_for_product(p: &Polynomial, x_var: Var, z_var: Var) -> Polynomial {
840 let max_degree = p
842 .terms()
843 .iter()
844 .map(|term| term.monomial.degree(x_var))
845 .max()
846 .unwrap_or(0);
847
848 let y_var = Y_VAR;
850
851 let terms: Vec<_> = p
852 .terms()
853 .iter()
854 .map(|term| {
855 let x_degree = term.monomial.degree(x_var);
856 let mut new_powers = Vec::new();
860
861 for vp in term.monomial.vars() {
863 if vp.var != x_var {
864 new_powers.push((vp.var, vp.power));
865 }
866 }
867
868 if max_degree > x_degree {
870 new_powers.push((y_var, max_degree - x_degree));
871 }
872
873 if x_degree > 0 {
875 new_powers.push((z_var, x_degree));
876 }
877
878 let new_monomial = if new_powers.is_empty() {
879 crate::polynomial::Monomial::unit()
880 } else {
881 crate::polynomial::Monomial::from_powers(new_powers)
882 };
883
884 crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
885 })
886 .collect();
887
888 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
889}
890
891#[cfg(test)]
892mod tests {
893 use super::*;
894
895 fn rat(n: i64) -> BigRational {
896 BigRational::from_integer(BigInt::from(n))
897 }
898
899 #[test]
900 fn test_algebraic_from_rational() {
901 let a = AlgebraicNumber::from_rational(rat(3));
902 assert!(a.is_positive());
903 assert_eq!(a.approximate(), rat(3));
904 }
905
906 #[test]
907 fn test_algebraic_sqrt() {
908 if let Some(a) = AlgebraicNumber::sqrt(&rat(4)) {
910 assert_eq!(a.approximate(), rat(2));
911 } else {
912 panic!("√4 should return a value");
914 }
915
916 if let Some(mut b) = AlgebraicNumber::sqrt(&rat(2)) {
918 for _ in 0..20 {
920 b.refine();
921 }
922
923 let approx = b.approximate();
925 assert!(approx.is_positive(), "√2 approximation should be positive");
927 assert!(
928 approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
929 "√2 approximation should be less than 2"
930 );
931
932 let poly = b.polynomial();
934 let val = poly.eval_at(b.var(), &approx);
935 let constant = val.constant_term().abs();
937 assert!(
938 constant < BigRational::from_integer(BigInt::from(1)),
939 "Polynomial evaluation at approximation should be small, got {}",
940 constant
941 );
942 } else {
943 panic!("√2 should return a value");
944 }
945 }
946
947 #[test]
948 fn test_algebraic_negate() {
949 let a = AlgebraicNumber::from_rational(rat(3));
950 let neg_a = a.negate();
951 assert_eq!(neg_a.approximate(), rat(-3));
952 }
953
954 #[test]
955 fn test_algebraic_add_rational() {
956 let a = AlgebraicNumber::from_rational(rat(3));
957 let b = a.add_rational(&rat(5));
958 assert_eq!(b.approximate(), rat(8));
959 }
960
961 #[test]
962 fn test_algebraic_mul_rational() {
963 let a = AlgebraicNumber::from_rational(rat(3));
964 let b = a.mul_rational(&rat(4));
965 assert_eq!(b.approximate(), rat(12));
966 }
967
968 #[test]
969 fn test_algebraic_cmp_rational() {
970 let mut a = AlgebraicNumber::from_rational(rat(3));
971 assert_eq!(a.cmp_rational(&rat(2)), Ordering::Greater);
972 assert_eq!(a.cmp_rational(&rat(3)), Ordering::Equal);
973 assert_eq!(a.cmp_rational(&rat(4)), Ordering::Less);
974 }
975
976 #[test]
977 fn test_algebraic_cmp() {
978 let mut a = AlgebraicNumber::from_rational(rat(2));
979 let mut b = AlgebraicNumber::from_rational(rat(3));
980 assert_eq!(a.cmp_algebraic(&mut b), Ordering::Less);
981 }
982
983 #[test]
984 fn test_algebraic_sign() {
985 let a = AlgebraicNumber::from_rational(rat(5));
986 assert_eq!(a.sign(), Some(1));
987
988 let b = AlgebraicNumber::from_rational(rat(-3));
989 assert_eq!(b.sign(), Some(-1));
990
991 let c = AlgebraicNumber::from_rational(rat(0));
992 assert_eq!(c.sign(), Some(0));
993 }
994
995 #[test]
996 fn test_algebraic_sub_rational() {
997 let a = AlgebraicNumber::from_rational(rat(10));
998 let b = a.sub_rational(&rat(3));
999 assert_eq!(b.approximate(), rat(7));
1000
1001 let c = AlgebraicNumber::from_rational(rat(5));
1002 let d = c.sub_rational(&rat(8));
1003 assert_eq!(d.approximate(), rat(-3));
1004 }
1005
1006 #[test]
1007 fn test_algebraic_inverse() {
1008 let a = AlgebraicNumber::from_rational(rat(4));
1010 let inv_a = a.inverse().expect("test operation should succeed");
1011 assert_eq!(
1012 inv_a.approximate(),
1013 BigRational::new(BigInt::from(1), BigInt::from(4))
1014 );
1015
1016 let b = AlgebraicNumber::from_rational(rat(-2));
1018 let inv_b = b.inverse().expect("test operation should succeed");
1019 assert_eq!(
1020 inv_b.approximate(),
1021 BigRational::new(BigInt::from(-1), BigInt::from(2))
1022 );
1023
1024 let c = AlgebraicNumber::from_rational(rat(0));
1026 assert!(c.inverse().is_none());
1027 }
1028
1029 #[test]
1030 fn test_algebraic_div_rational() {
1031 let a = AlgebraicNumber::from_rational(rat(10));
1033 let b = a
1034 .div_rational(&rat(2))
1035 .expect("test operation should succeed");
1036 assert_eq!(b.approximate(), rat(5));
1037
1038 let c = AlgebraicNumber::from_rational(rat(6));
1040 let d = c
1041 .div_rational(&rat(4))
1042 .expect("test operation should succeed");
1043 assert_eq!(
1044 d.approximate(),
1045 BigRational::new(BigInt::from(3), BigInt::from(2))
1046 );
1047
1048 let e = AlgebraicNumber::from_rational(rat(5));
1050 assert!(e.div_rational(&rat(0)).is_none());
1051 }
1052
1053 #[test]
1054 fn test_algebraic_pow() {
1055 let a = AlgebraicNumber::from_rational(rat(2));
1057 let b = a.pow(3).expect("test operation should succeed");
1058 assert_eq!(b.approximate(), rat(8));
1059
1060 let c = AlgebraicNumber::from_rational(rat(3));
1062 let d = c.pow(0).expect("test operation should succeed");
1063 assert_eq!(d.approximate(), rat(1));
1064
1065 let e = AlgebraicNumber::from_rational(rat(2));
1067 let f = e.pow(-1).expect("test operation should succeed");
1068 assert_eq!(
1069 f.approximate(),
1070 BigRational::new(BigInt::from(1), BigInt::from(2))
1071 );
1072
1073 let g = AlgebraicNumber::from_rational(rat(0));
1075 assert!(g.pow(-1).is_none());
1076 }
1077
1078 #[test]
1079 fn test_algebraic_refine() {
1080 let a = AlgebraicNumber::from_rational(rat(5));
1081 let (lo1, hi1) = a.interval();
1082 assert_eq!(lo1, hi1); if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
1086 let (lo1, hi1) = sqrt2.interval();
1087 let width1 = hi1 - lo1;
1088
1089 sqrt2.refine();
1090 let (lo2, hi2) = sqrt2.interval();
1091 let width2 = hi2 - lo2;
1092
1093 assert!(width2 < width1);
1095 }
1096 }
1097
1098 #[test]
1099 fn test_algebraic_approximate_with_precision() {
1100 if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
1101 let epsilon = BigRational::new(BigInt::from(1), BigInt::from(100));
1102 let approx = sqrt2.approximate_with_precision(&epsilon);
1103
1104 let (lo, hi) = sqrt2.interval();
1106 assert!(
1107 hi - lo < epsilon,
1108 "Interval width {} should be less than {}",
1109 hi - lo,
1110 epsilon
1111 );
1112
1113 assert!(approx.is_positive(), "√2 approximation should be positive");
1115 assert!(
1116 approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
1117 "√2 approximation should be less than 2"
1118 );
1119 }
1120 }
1121
1122 #[test]
1123 fn test_algebraic_add_algebraic() {
1124 let mut a = AlgebraicNumber::from_rational(rat(2));
1127 let mut b = AlgebraicNumber::from_rational(rat(3));
1128 let c = a.add_algebraic(&mut b);
1129 assert_eq!(c.approximate(), rat(5));
1130 }
1131
1132 #[test]
1133 fn test_algebraic_mul_algebraic() {
1134 let mut a = AlgebraicNumber::from_rational(rat(2));
1137 let mut b = AlgebraicNumber::from_rational(rat(3));
1138 let c = a.mul_algebraic(&mut b);
1139 assert_eq!(c.approximate(), rat(6));
1140 }
1141
1142 #[test]
1143 fn test_algebraic_add_irrational() {
1144 let mut sqrt2_a = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1148 let mut sqrt2_b = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1149
1150 let mut sum = sqrt2_a.add_algebraic(&mut sqrt2_b);
1151
1152 assert!(!sum.is_rational(), "√2 + √2 = 2√2 is irrational");
1154
1155 let x2_minus_8 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-8, &[])]);
1157 let g = sum.polynomial().gcd_univariate(&x2_minus_8);
1158 assert!(
1159 g.degree(sum.var()) >= 1,
1160 "sum's polynomial must share the root 2√2 (root of x²-8)"
1161 );
1162
1163 for _ in 0..40 {
1165 sum.refine();
1166 }
1167 let approx = sum.approximate();
1168 assert!(
1169 approx > rat(2) && approx < rat(3),
1170 "2√2 ≈ 2.83, got {approx}"
1171 );
1172 }
1173
1174 #[test]
1175 fn test_algebraic_mul_irrational() {
1176 let mut sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1179 let mut sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1180
1181 let mut product = sqrt2.mul_algebraic(&mut sqrt3);
1182
1183 assert!(!product.is_rational(), "√2 · √3 = √6 is irrational");
1184
1185 let x2_minus_6 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-6, &[])]);
1186 let g = product.polynomial().gcd_univariate(&x2_minus_6);
1187 assert!(
1188 g.degree(product.var()) >= 1,
1189 "product's polynomial must share the root √6 (root of x²-6)"
1190 );
1191
1192 for _ in 0..40 {
1193 product.refine();
1194 }
1195 let approx = product.approximate();
1196 assert!(
1197 approx > rat(2) && approx < rat(3),
1198 "√6 ≈ 2.45, got {approx}"
1199 );
1200 }
1201
1202 #[test]
1203 fn test_algebraic_add_mixed() {
1204 let sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1206
1207 let sum = sqrt2.add_rational(&rat(1));
1209
1210 assert!(sum.is_positive());
1212
1213 let approx = sum.approximate();
1215 assert!(approx > rat(2), "1 + √2 should be > 2, got {}", approx);
1216 }
1217
1218 #[test]
1219 fn test_algebraic_mul_by_rational() {
1220 let sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1222
1223 assert!(!sqrt3.is_negative(), "√3 should not be negative");
1225
1226 let product = sqrt3.mul_rational(&rat(2));
1228
1229 let (lo, hi) = product.interval();
1231 assert!(
1232 !lo.is_negative(),
1233 "Lower bound should be non-negative, got {}",
1234 lo
1235 );
1236 assert!(
1237 hi.is_positive(),
1238 "Upper bound should be positive, got {}",
1239 hi
1240 );
1241
1242 let approx = product.approximate();
1244 assert!(approx > rat(3), "2 * √3 should be > 3, got {}", approx);
1245 }
1246}