1use std::cmp::Ordering;
19
20use num_bigint::{BigInt, Sign};
21use num_rational::BigRational;
22use num_traits::{One, Signed, ToPrimitive, Zero};
23
24pub type Rational = BigRational;
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct Ball {
30 pub lo: Rational,
31 pub hi: Rational,
32}
33
34impl Ball {
35 pub fn new(lo: Rational, hi: Rational) -> Self {
37 if lo <= hi {
38 Ball { lo, hi }
39 } else {
40 Ball { lo: hi, hi: lo }
41 }
42 }
43
44 pub fn point(r: &Rational) -> Self {
46 Ball { lo: r.clone(), hi: r.clone() }
47 }
48
49 pub fn from_i64(n: i64) -> Self {
51 Self::point(&BigRational::from_integer(BigInt::from(n)))
52 }
53
54 pub fn sign(&self) -> Option<Sign> {
57 if self.lo.is_positive() {
58 Some(Sign::Plus)
59 } else if self.hi.is_negative() {
60 Some(Sign::Minus)
61 } else if self.lo.is_zero() && self.hi.is_zero() {
62 Some(Sign::NoSign)
63 } else {
64 None
65 }
66 }
67
68 pub fn cmp(&self, other: &Ball) -> Option<Ordering> {
70 if self.hi < other.lo {
71 Some(Ordering::Less)
72 } else if self.lo > other.hi {
73 Some(Ordering::Greater)
74 } else if self.lo == self.hi && other.lo == other.hi && self.lo == other.lo {
75 Some(Ordering::Equal)
76 } else {
77 None
78 }
79 }
80
81 pub fn contains_zero(&self) -> bool {
83 !self.lo.is_positive() && !self.hi.is_negative()
84 }
85
86 pub fn contains(&self, v: &Rational) -> bool {
88 self.lo <= *v && *v <= self.hi
89 }
90
91 pub fn width(&self) -> Rational {
93 &self.hi - &self.lo
94 }
95
96 pub fn midpoint(&self) -> Rational {
98 (&self.lo + &self.hi) / BigRational::from_integer(BigInt::from(2))
99 }
100
101 pub fn to_f64(&self) -> f64 {
103 rational_to_f64(&self.midpoint())
104 }
105
106 pub fn add(&self, b: &Ball) -> Ball {
108 Ball::new(&self.lo + &b.lo, &self.hi + &b.hi)
109 }
110
111 pub fn neg(&self) -> Ball {
113 Ball::new(-&self.hi, -&self.lo)
114 }
115
116 pub fn sub(&self, b: &Ball) -> Ball {
118 Ball::new(&self.lo - &b.hi, &self.hi - &b.lo)
119 }
120
121 pub fn mul(&self, b: &Ball) -> Ball {
123 let products = [
124 &self.lo * &b.lo,
125 &self.lo * &b.hi,
126 &self.hi * &b.lo,
127 &self.hi * &b.hi,
128 ];
129 let mut min = products[0].clone();
130 let mut max = products[0].clone();
131 for p in &products[1..] {
132 if *p < min {
133 min = p.clone();
134 }
135 if *p > max {
136 max = p.clone();
137 }
138 }
139 Ball::new(min, max)
140 }
141
142 pub fn recip(&self) -> Option<Ball> {
144 if self.contains_zero() {
145 return None;
146 }
147 let one = BigRational::one();
148 Some(Ball::new(&one / &self.hi, &one / &self.lo))
149 }
150}
151
152pub fn rational_to_f64(r: &Rational) -> f64 {
155 let p = r.numer();
156 let q = r.denom();
157 if q.is_zero() {
158 return f64::NAN;
159 }
160 if p.is_zero() {
161 return 0.0;
162 }
163 let negative = (p.sign() == Sign::Minus) ^ (q.sign() == Sign::Minus);
164 let mut pm = p.magnitude().clone();
165 let mut qm = q.magnitude().clone();
166 let pb = pm.bits() as i64;
167 let qb = qm.bits() as i64;
168 let shift = 60 + qb - pb;
169 if shift > 0 {
170 pm <<= shift as u64;
171 } else {
172 qm <<= (-shift) as u64;
173 }
174 let quo = &pm / &qm;
175 let mag = quo.to_f64().unwrap_or(f64::INFINITY);
176 let value = mag * 2f64.powi(-(shift as i32));
177 if negative {
178 -value
179 } else {
180 value
181 }
182}
183
184pub fn rat(p: i64, q: i64) -> Rational {
186 BigRational::new(BigInt::from(p), BigInt::from(q))
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn sign_and_straddle() {
195 assert_eq!(Ball::new(rat(1, 2), rat(3, 2)).sign(), Some(Sign::Plus));
196 assert_eq!(Ball::new(rat(-3, 2), rat(-1, 2)).sign(), Some(Sign::Minus));
197 assert_eq!(Ball::new(rat(-1, 1), rat(1, 1)).sign(), None);
198 assert_eq!(Ball::from_i64(0).sign(), Some(Sign::NoSign));
199 }
200
201 #[test]
202 fn ordering() {
203 let a = Ball::from_i64(1);
204 let b = Ball::from_i64(2);
205 assert_eq!(a.cmp(&b), Some(Ordering::Less));
206 assert_eq!(b.cmp(&a), Some(Ordering::Greater));
207 assert_eq!(Ball::new(rat(0, 1), rat(2, 1)).cmp(&Ball::new(rat(1, 1), rat(3, 1))), None);
209 }
210
211 #[test]
212 fn interval_arith() {
213 let a = Ball::new(rat(1, 1), rat(2, 1));
214 let b = Ball::new(rat(3, 1), rat(4, 1));
215 assert_eq!(a.add(&b), Ball::new(rat(4, 1), rat(6, 1)));
216 assert_eq!(a.mul(&b), Ball::new(rat(3, 1), rat(8, 1)));
217 assert!(a.recip().is_some());
218 assert!(Ball::new(rat(-1, 1), rat(1, 1)).recip().is_none());
219 }
220
221 #[test]
222 fn to_float() {
223 assert!((Ball::new(rat(1, 3), rat(1, 3)).to_f64() - (1.0 / 3.0)).abs() < 1e-15);
224 }
225}