1use crate::polynomial::{Polynomial, Var};
10#[allow(unused_imports)]
11use crate::prelude::*;
12use core::cmp::Ordering;
13use num_bigint::BigInt;
14use num_rational::BigRational;
15use num_traits::{Signed, Zero};
16
17#[derive(Clone, Debug)]
25pub struct AlgebraicNumber {
26 polynomial: Polynomial,
29
30 var: Var,
32
33 lower: BigRational,
35
36 upper: BigRational,
38}
39
40impl AlgebraicNumber {
41 pub fn new(polynomial: Polynomial, var: Var, lower: BigRational, upper: BigRational) -> Self {
46 let num_roots = polynomial.count_roots_in_interval(var, &lower, &upper);
48 assert_eq!(
49 num_roots, 1,
50 "Interval must contain exactly one root, found {}",
51 num_roots
52 );
53
54 Self {
55 polynomial: polynomial.primitive(),
56 var,
57 lower,
58 upper,
59 }
60 }
61
62 pub fn from_rational(r: BigRational) -> Self {
64 let poly = Polynomial::from_var(0).sub(&Polynomial::constant(r.clone()));
66
67 Self {
68 polynomial: poly,
69 var: 0,
70 lower: r.clone(),
71 upper: r,
72 }
73 }
74
75 pub fn sqrt(n: &BigRational) -> Option<Self> {
79 if n.is_negative() {
80 return None;
81 }
82
83 if n.is_zero() {
84 return Some(Self::from_rational(BigRational::zero()));
85 }
86
87 if let Some(sqrt_n) = crate::polynomial::rational_sqrt(n) {
89 return Some(Self::from_rational(sqrt_n));
90 }
91
92 let poly =
94 Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]).sub(&Polynomial::constant(n.clone()));
95
96 let roots = poly.isolate_roots(0);
98
99 for (lo, hi) in roots {
101 let mid = (&lo + &hi) / BigRational::from_integer(BigInt::from(2));
102
103 if mid.is_positive() {
105 let adjusted_lo = if lo.is_negative() {
108 BigRational::zero()
109 } else {
110 lo
111 };
112
113 if poly.count_roots_in_interval(0, &adjusted_lo, &hi) == 1 {
115 return Some(Self::new(poly.clone(), 0, adjusted_lo, hi));
116 }
117 }
118 }
119
120 None
121 }
122
123 pub fn polynomial(&self) -> &Polynomial {
125 &self.polynomial
126 }
127
128 pub fn interval(&self) -> (&BigRational, &BigRational) {
130 (&self.lower, &self.upper)
131 }
132
133 pub fn var(&self) -> Var {
135 self.var
136 }
137
138 pub fn refine(&mut self) {
142 let mid = (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2));
143
144 let val_mid = self.polynomial.eval_at(self.var, &mid);
145
146 if val_mid.constant_term().is_zero() {
147 self.lower = mid.clone();
149 self.upper = mid;
150 } else {
151 let val_lo = self.polynomial.eval_at(self.var, &self.lower);
153 let val_mid = self.polynomial.eval_at(self.var, &mid);
154
155 let sign_lo = val_lo.constant_term().signum();
156 let sign_mid = val_mid.constant_term().signum();
157
158 if sign_lo != sign_mid {
159 self.upper = mid;
161 } else {
162 self.lower = mid;
164 }
165 }
166 }
167
168 pub fn approximate(&self) -> BigRational {
172 (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2))
173 }
174
175 pub fn approximate_with_precision(&mut self, epsilon: &BigRational) -> BigRational {
179 while &self.upper - &self.lower > *epsilon {
180 self.refine();
181 }
182 self.approximate()
183 }
184
185 pub fn is_zero(&self) -> bool {
187 self.lower.is_zero() && self.upper.is_zero()
188 }
189
190 pub fn is_positive(&self) -> bool {
192 self.lower.is_positive()
193 }
194
195 pub fn is_negative(&self) -> bool {
197 self.upper.is_negative()
198 }
199
200 pub fn is_rational(&self) -> bool {
205 let degree = self.polynomial.degree(self.var);
208 degree <= 1 || self.lower == self.upper
209 }
210
211 pub fn sign(&self) -> Option<i8> {
219 if self.is_zero() {
220 Some(0)
221 } else if self.is_positive() {
222 Some(1)
223 } else if self.is_negative() {
224 Some(-1)
225 } else {
226 None
227 }
228 }
229
230 pub fn cmp_algebraic(&mut self, other: &mut AlgebraicNumber) -> Ordering {
232 let max_iterations = 1000;
234 let mut iterations = 0;
235
236 while iterations < max_iterations {
237 iterations += 1;
238
239 if self.upper < other.lower {
241 return Ordering::Less;
242 }
243 if self.lower > other.upper {
244 return Ordering::Greater;
245 }
246 if self.lower == self.upper && other.lower == other.upper && self.lower == other.lower {
247 return Ordering::Equal;
248 }
249
250 self.refine();
252 other.refine();
253 }
254
255 self.approximate().cmp(&other.approximate())
258 }
259
260 pub fn cmp_rational(&mut self, r: &BigRational) -> Ordering {
262 let max_iterations = 1000;
264 let mut iterations = 0;
265
266 while iterations < max_iterations {
267 iterations += 1;
268
269 if &self.upper < r {
270 return Ordering::Less;
271 }
272 if &self.lower > r {
273 return Ordering::Greater;
274 }
275 if &self.lower == r && &self.upper == r {
276 return Ordering::Equal;
277 }
278
279 self.refine();
280 }
281
282 self.approximate().cmp(r)
284 }
285
286 pub fn negate(&self) -> AlgebraicNumber {
290 let negated_poly = negate_polynomial(&self.polynomial, self.var);
292
293 AlgebraicNumber {
294 polynomial: negated_poly,
295 var: self.var,
296 lower: -self.upper.clone(),
297 upper: -self.lower.clone(),
298 }
299 }
300
301 pub fn add_rational(&self, r: &BigRational) -> AlgebraicNumber {
303 let shifted_poly = self.polynomial.substitute(
305 self.var,
306 &Polynomial::from_var(self.var).sub(&Polynomial::constant(r.clone())),
307 );
308
309 AlgebraicNumber {
310 polynomial: shifted_poly,
311 var: self.var,
312 lower: &self.lower + r,
313 upper: &self.upper + r,
314 }
315 }
316
317 pub fn mul_rational(&self, r: &BigRational) -> AlgebraicNumber {
319 if r.is_zero() {
320 return Self::from_rational(BigRational::zero());
321 }
322
323 let scaled_poly = scale_polynomial_var(&self.polynomial, self.var, r);
326
327 let (new_lower, new_upper) = if r.is_positive() {
328 (&self.lower * r, &self.upper * r)
329 } else {
330 (&self.upper * r, &self.lower * r)
331 };
332
333 AlgebraicNumber {
334 polynomial: scaled_poly,
335 var: self.var,
336 lower: new_lower,
337 upper: new_upper,
338 }
339 }
340
341 pub fn sub_rational(&self, r: &BigRational) -> AlgebraicNumber {
343 self.add_rational(&(-r))
345 }
346
347 pub fn inverse(&self) -> Option<AlgebraicNumber> {
351 if self.is_zero() {
352 return None;
353 }
354
355 let inv_poly = reciprocal_polynomial(&self.polynomial, self.var);
358
359 let (new_lower, new_upper) = if self.is_positive() || self.is_negative() {
363 (
364 BigRational::from_integer(BigInt::from(1)) / &self.upper,
365 BigRational::from_integer(BigInt::from(1)) / &self.lower,
366 )
367 } else {
368 return None;
370 };
371
372 Some(AlgebraicNumber {
373 polynomial: inv_poly,
374 var: self.var,
375 lower: new_lower,
376 upper: new_upper,
377 })
378 }
379
380 pub fn div_rational(&self, r: &BigRational) -> Option<AlgebraicNumber> {
384 if r.is_zero() {
385 return None;
386 }
387
388 Some(self.mul_rational(&(BigRational::from_integer(BigInt::from(1)) / r)))
390 }
391
392 pub fn pow(&self, n: i32) -> Option<AlgebraicNumber> {
394 if n == 0 {
395 return Some(Self::from_rational(BigRational::from_integer(
396 BigInt::from(1),
397 )));
398 }
399
400 if n < 0 {
401 return self.inverse()?.pow(-n);
403 }
404
405 let mut result = Self::from_rational(BigRational::from_integer(BigInt::from(1)));
407 let mut base = self.clone();
408 let mut exp = n as u32;
409
410 while exp > 0 {
411 if exp % 2 == 1 {
412 let approx = base.approximate() * result.approximate();
415 result = Self::from_rational(approx);
416 }
417 if exp > 1 {
418 let approx = base.approximate() * base.approximate();
420 base = Self::from_rational(approx);
421 }
422 exp /= 2;
423 }
424
425 Some(result)
426 }
427
428 #[allow(dead_code)]
437 pub fn add_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
438 if self.is_rational() {
440 return other.add_rational(&self.approximate());
441 }
442 if other.is_rational() {
443 return self.add_rational(&other.approximate());
444 }
445
446 for _ in 0..20 {
448 self.refine();
449 other.refine();
450 }
451
452 let approx_sum = self.approximate() + other.approximate();
454
455 Self::from_rational(approx_sum)
461 }
462
463 #[allow(dead_code)]
472 pub fn mul_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
473 if self.is_rational() {
475 return other.mul_rational(&self.approximate());
476 }
477 if other.is_rational() {
478 return self.mul_rational(&other.approximate());
479 }
480
481 let approx_self = self.approximate();
483 let approx_other = other.approximate();
484 if approx_self.is_zero() {
485 return Self::from_rational(BigRational::zero());
486 }
487 if approx_other.is_zero() {
488 return Self::from_rational(BigRational::zero());
489 }
490
491 for _ in 0..20 {
493 self.refine();
494 other.refine();
495 }
496
497 let approx_product = approx_self * approx_other;
499
500 Self::from_rational(approx_product)
506 }
507}
508
509fn negate_polynomial(p: &Polynomial, var: Var) -> Polynomial {
511 let terms: Vec<_> = p
512 .terms()
513 .iter()
514 .map(|term| {
515 let degree = term.monomial.degree(var);
516 let coeff = if degree % 2 == 1 {
517 -term.coeff.clone()
518 } else {
519 term.coeff.clone()
520 };
521 crate::polynomial::Term::new(coeff, term.monomial.clone())
522 })
523 .collect();
524
525 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
526}
527
528fn scale_polynomial_var(p: &Polynomial, var: Var, r: &BigRational) -> Polynomial {
530 if r.is_zero() {
531 return Polynomial::zero();
532 }
533
534 let terms: Vec<_> = p
535 .terms()
536 .iter()
537 .map(|term| {
538 let degree = term.monomial.degree(var);
539 let new_coeff = &term.coeff / r.pow(degree as i32);
541 crate::polynomial::Term::new(new_coeff, term.monomial.clone())
542 })
543 .collect();
544
545 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
546}
547
548fn reciprocal_polynomial(p: &Polynomial, var: Var) -> Polynomial {
552 let max_degree = p
554 .terms()
555 .iter()
556 .map(|term| term.monomial.degree(var))
557 .max()
558 .unwrap_or(0);
559
560 let terms: Vec<_> = p
561 .terms()
562 .iter()
563 .map(|term| {
564 let degree = term.monomial.degree(var);
565 let new_powers: Vec<(Var, u32)> = term
569 .monomial
570 .vars()
571 .iter()
572 .map(|vp| {
573 if vp.var == var {
574 (vp.var, max_degree - degree)
575 } else {
576 (vp.var, vp.power)
577 }
578 })
579 .collect();
580
581 let new_monomial =
582 if new_powers.is_empty() || (new_powers.len() == 1 && new_powers[0].1 == 0) {
583 crate::polynomial::Monomial::unit()
584 } else {
585 crate::polynomial::Monomial::from_powers(new_powers)
586 };
587
588 crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
589 })
590 .collect();
591
592 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
593}
594
595#[allow(dead_code)]
599fn shift_var(p: &Polynomial, old_var: Var, new_var: Var) -> Polynomial {
600 if old_var == new_var {
601 return p.clone();
602 }
603
604 let terms: Vec<_> = p
605 .terms()
606 .iter()
607 .map(|term| {
608 let new_powers: Vec<(Var, u32)> = term
609 .monomial
610 .vars()
611 .iter()
612 .map(|vp| {
613 if vp.var == old_var {
614 (new_var, vp.power)
615 } else {
616 (vp.var, vp.power)
617 }
618 })
619 .collect();
620
621 let new_monomial = if new_powers.is_empty() {
622 crate::polynomial::Monomial::unit()
623 } else {
624 crate::polynomial::Monomial::from_powers(new_powers)
625 };
626
627 crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
628 })
629 .collect();
630
631 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
632}
633
634#[allow(dead_code)]
639fn find_closest_root(
640 roots: &[(BigRational, BigRational)],
641 target: &BigRational,
642) -> Option<(BigRational, BigRational)> {
643 if roots.is_empty() {
644 return None;
645 }
646
647 let mut best_root = None;
648 let mut best_distance: Option<BigRational> = None;
649
650 for (lo, hi) in roots {
651 let mid = (lo + hi) / BigRational::from_integer(BigInt::from(2));
653 let distance = (mid - target).abs();
654
655 match &best_distance {
656 None => {
657 best_distance = Some(distance);
658 best_root = Some((lo.clone(), hi.clone()));
659 }
660 Some(d) => {
661 if distance < *d {
662 best_distance = Some(distance);
663 best_root = Some((lo.clone(), hi.clone()));
664 }
665 }
666 }
667 }
668
669 best_root
670}
671
672#[allow(dead_code)]
677fn scale_for_product(p: &Polynomial, x_var: Var, z_var: Var) -> Polynomial {
678 let max_degree = p
680 .terms()
681 .iter()
682 .map(|term| term.monomial.degree(x_var))
683 .max()
684 .unwrap_or(0);
685
686 let y_var = 1;
688
689 let terms: Vec<_> = p
690 .terms()
691 .iter()
692 .map(|term| {
693 let x_degree = term.monomial.degree(x_var);
694 let mut new_powers = Vec::new();
698
699 for vp in term.monomial.vars() {
701 if vp.var != x_var {
702 new_powers.push((vp.var, vp.power));
703 }
704 }
705
706 if max_degree > x_degree {
708 new_powers.push((y_var, max_degree - x_degree));
709 }
710
711 if x_degree > 0 {
713 new_powers.push((z_var, x_degree));
714 }
715
716 let new_monomial = if new_powers.is_empty() {
717 crate::polynomial::Monomial::unit()
718 } else {
719 crate::polynomial::Monomial::from_powers(new_powers)
720 };
721
722 crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
723 })
724 .collect();
725
726 Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732
733 fn rat(n: i64) -> BigRational {
734 BigRational::from_integer(BigInt::from(n))
735 }
736
737 #[test]
738 fn test_algebraic_from_rational() {
739 let a = AlgebraicNumber::from_rational(rat(3));
740 assert!(a.is_positive());
741 assert_eq!(a.approximate(), rat(3));
742 }
743
744 #[test]
745 fn test_algebraic_sqrt() {
746 if let Some(a) = AlgebraicNumber::sqrt(&rat(4)) {
748 assert_eq!(a.approximate(), rat(2));
749 } else {
750 panic!("√4 should return a value");
752 }
753
754 if let Some(mut b) = AlgebraicNumber::sqrt(&rat(2)) {
756 for _ in 0..20 {
758 b.refine();
759 }
760
761 let approx = b.approximate();
763 assert!(approx.is_positive(), "√2 approximation should be positive");
765 assert!(
766 approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
767 "√2 approximation should be less than 2"
768 );
769
770 let poly = b.polynomial();
772 let val = poly.eval_at(b.var(), &approx);
773 let constant = val.constant_term().abs();
775 assert!(
776 constant < BigRational::from_integer(BigInt::from(1)),
777 "Polynomial evaluation at approximation should be small, got {}",
778 constant
779 );
780 } else {
781 panic!("√2 should return a value");
782 }
783 }
784
785 #[test]
786 fn test_algebraic_negate() {
787 let a = AlgebraicNumber::from_rational(rat(3));
788 let neg_a = a.negate();
789 assert_eq!(neg_a.approximate(), rat(-3));
790 }
791
792 #[test]
793 fn test_algebraic_add_rational() {
794 let a = AlgebraicNumber::from_rational(rat(3));
795 let b = a.add_rational(&rat(5));
796 assert_eq!(b.approximate(), rat(8));
797 }
798
799 #[test]
800 fn test_algebraic_mul_rational() {
801 let a = AlgebraicNumber::from_rational(rat(3));
802 let b = a.mul_rational(&rat(4));
803 assert_eq!(b.approximate(), rat(12));
804 }
805
806 #[test]
807 fn test_algebraic_cmp_rational() {
808 let mut a = AlgebraicNumber::from_rational(rat(3));
809 assert_eq!(a.cmp_rational(&rat(2)), Ordering::Greater);
810 assert_eq!(a.cmp_rational(&rat(3)), Ordering::Equal);
811 assert_eq!(a.cmp_rational(&rat(4)), Ordering::Less);
812 }
813
814 #[test]
815 fn test_algebraic_cmp() {
816 let mut a = AlgebraicNumber::from_rational(rat(2));
817 let mut b = AlgebraicNumber::from_rational(rat(3));
818 assert_eq!(a.cmp_algebraic(&mut b), Ordering::Less);
819 }
820
821 #[test]
822 fn test_algebraic_sign() {
823 let a = AlgebraicNumber::from_rational(rat(5));
824 assert_eq!(a.sign(), Some(1));
825
826 let b = AlgebraicNumber::from_rational(rat(-3));
827 assert_eq!(b.sign(), Some(-1));
828
829 let c = AlgebraicNumber::from_rational(rat(0));
830 assert_eq!(c.sign(), Some(0));
831 }
832
833 #[test]
834 fn test_algebraic_sub_rational() {
835 let a = AlgebraicNumber::from_rational(rat(10));
836 let b = a.sub_rational(&rat(3));
837 assert_eq!(b.approximate(), rat(7));
838
839 let c = AlgebraicNumber::from_rational(rat(5));
840 let d = c.sub_rational(&rat(8));
841 assert_eq!(d.approximate(), rat(-3));
842 }
843
844 #[test]
845 fn test_algebraic_inverse() {
846 let a = AlgebraicNumber::from_rational(rat(4));
848 let inv_a = a.inverse().expect("test operation should succeed");
849 assert_eq!(
850 inv_a.approximate(),
851 BigRational::new(BigInt::from(1), BigInt::from(4))
852 );
853
854 let b = AlgebraicNumber::from_rational(rat(-2));
856 let inv_b = b.inverse().expect("test operation should succeed");
857 assert_eq!(
858 inv_b.approximate(),
859 BigRational::new(BigInt::from(-1), BigInt::from(2))
860 );
861
862 let c = AlgebraicNumber::from_rational(rat(0));
864 assert!(c.inverse().is_none());
865 }
866
867 #[test]
868 fn test_algebraic_div_rational() {
869 let a = AlgebraicNumber::from_rational(rat(10));
871 let b = a
872 .div_rational(&rat(2))
873 .expect("test operation should succeed");
874 assert_eq!(b.approximate(), rat(5));
875
876 let c = AlgebraicNumber::from_rational(rat(6));
878 let d = c
879 .div_rational(&rat(4))
880 .expect("test operation should succeed");
881 assert_eq!(
882 d.approximate(),
883 BigRational::new(BigInt::from(3), BigInt::from(2))
884 );
885
886 let e = AlgebraicNumber::from_rational(rat(5));
888 assert!(e.div_rational(&rat(0)).is_none());
889 }
890
891 #[test]
892 fn test_algebraic_pow() {
893 let a = AlgebraicNumber::from_rational(rat(2));
895 let b = a.pow(3).expect("test operation should succeed");
896 assert_eq!(b.approximate(), rat(8));
897
898 let c = AlgebraicNumber::from_rational(rat(3));
900 let d = c.pow(0).expect("test operation should succeed");
901 assert_eq!(d.approximate(), rat(1));
902
903 let e = AlgebraicNumber::from_rational(rat(2));
905 let f = e.pow(-1).expect("test operation should succeed");
906 assert_eq!(
907 f.approximate(),
908 BigRational::new(BigInt::from(1), BigInt::from(2))
909 );
910
911 let g = AlgebraicNumber::from_rational(rat(0));
913 assert!(g.pow(-1).is_none());
914 }
915
916 #[test]
917 fn test_algebraic_refine() {
918 let a = AlgebraicNumber::from_rational(rat(5));
919 let (lo1, hi1) = a.interval();
920 assert_eq!(lo1, hi1); if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
924 let (lo1, hi1) = sqrt2.interval();
925 let width1 = hi1 - lo1;
926
927 sqrt2.refine();
928 let (lo2, hi2) = sqrt2.interval();
929 let width2 = hi2 - lo2;
930
931 assert!(width2 < width1);
933 }
934 }
935
936 #[test]
937 fn test_algebraic_approximate_with_precision() {
938 if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
939 let epsilon = BigRational::new(BigInt::from(1), BigInt::from(100));
940 let approx = sqrt2.approximate_with_precision(&epsilon);
941
942 let (lo, hi) = sqrt2.interval();
944 assert!(
945 hi - lo < epsilon,
946 "Interval width {} should be less than {}",
947 hi - lo,
948 epsilon
949 );
950
951 assert!(approx.is_positive(), "√2 approximation should be positive");
953 assert!(
954 approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
955 "√2 approximation should be less than 2"
956 );
957 }
958 }
959
960 #[test]
961 fn test_algebraic_add_algebraic() {
962 let mut a = AlgebraicNumber::from_rational(rat(2));
965 let mut b = AlgebraicNumber::from_rational(rat(3));
966 let c = a.add_algebraic(&mut b);
967 assert_eq!(c.approximate(), rat(5));
968 }
969
970 #[test]
971 fn test_algebraic_mul_algebraic() {
972 let mut a = AlgebraicNumber::from_rational(rat(2));
975 let mut b = AlgebraicNumber::from_rational(rat(3));
976 let c = a.mul_algebraic(&mut b);
977 assert_eq!(c.approximate(), rat(6));
978 }
979
980 #[test]
981 fn test_algebraic_add_irrational() {
982 let mut sqrt2_a = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
986 let mut sqrt2_b = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
987
988 let sum = sqrt2_a.add_algebraic(&mut sqrt2_b);
990
991 let _ = sum.approximate();
994 }
995
996 #[test]
997 fn test_algebraic_mul_irrational() {
998 let mut sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1002 let mut sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1003
1004 let product = sqrt2.mul_algebraic(&mut sqrt3);
1006
1007 let _ = product.approximate();
1010 }
1011
1012 #[test]
1013 fn test_algebraic_add_mixed() {
1014 let sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1016
1017 let sum = sqrt2.add_rational(&rat(1));
1019
1020 assert!(sum.is_positive());
1022
1023 let approx = sum.approximate();
1025 assert!(approx > rat(2), "1 + √2 should be > 2, got {}", approx);
1026 }
1027
1028 #[test]
1029 fn test_algebraic_mul_by_rational() {
1030 let sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1032
1033 assert!(!sqrt3.is_negative(), "√3 should not be negative");
1035
1036 let product = sqrt3.mul_rational(&rat(2));
1038
1039 let (lo, hi) = product.interval();
1041 assert!(
1042 !lo.is_negative(),
1043 "Lower bound should be non-negative, got {}",
1044 lo
1045 );
1046 assert!(
1047 hi.is_positive(),
1048 "Upper bound should be positive, got {}",
1049 hi
1050 );
1051
1052 let approx = product.approximate();
1054 assert!(approx > rat(3), "2 * √3 should be > 3, got {}", approx);
1055 }
1056}