scirs2_special/
validated.rs1#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct Ball {
16 pub mid: f64,
18 pub rad: f64,
20}
21
22impl Ball {
23 #[inline]
28 pub fn new(mid: f64, rad: f64) -> Self {
29 debug_assert!(rad >= 0.0, "Ball radius must be non-negative");
30 Ball {
31 mid,
32 rad: rad.abs(), }
34 }
35
36 #[inline]
38 pub fn from_exact(x: f64) -> Self {
39 Ball { mid: x, rad: 0.0 }
40 }
41
42 pub fn from_interval(lo: f64, hi: f64) -> Option<Self> {
46 if lo > hi {
47 return None;
48 }
49 let mid = (lo + hi) * 0.5;
50 let rad = (hi - lo) * 0.5;
51 Some(Ball::new(mid, rad))
52 }
53
54 #[inline]
56 pub fn lo(&self) -> f64 {
57 self.mid - self.rad
58 }
59
60 #[inline]
62 pub fn hi(&self) -> f64 {
63 self.mid + self.rad
64 }
65
66 #[inline]
68 pub fn contains(&self, x: f64) -> bool {
69 x >= self.lo() && x <= self.hi()
70 }
71
72 #[inline]
74 pub fn overlaps(&self, other: &Ball) -> bool {
75 self.lo() <= other.hi() && other.lo() <= self.hi()
76 }
77
78 #[inline]
80 pub fn is_valid(&self) -> bool {
81 self.rad >= 0.0 && self.mid.is_finite()
82 }
83
84 #[inline]
88 pub fn add(&self, other: &Ball) -> Self {
89 let mid = self.mid + other.mid;
90 let prop = self.rad + other.rad;
91 let fp_err = mid.abs() * f64::EPSILON * 2.0;
92 Ball::new(mid, prop + fp_err)
93 }
94
95 #[inline]
97 pub fn sub(&self, other: &Ball) -> Self {
98 let mid = self.mid - other.mid;
99 let prop = self.rad + other.rad;
100 let fp_err = mid.abs() * f64::EPSILON * 2.0;
101 Ball::new(mid, prop + fp_err)
102 }
103
104 #[inline]
110 pub fn mul(&self, other: &Ball) -> Self {
111 let mid = self.mid * other.mid;
112 let prop = self.mid.abs() * other.rad + other.mid.abs() * self.rad + self.rad * other.rad;
113 let fp_err = mid.abs() * f64::EPSILON * 2.0;
114 Ball::new(mid, prop + fp_err)
115 }
116
117 pub fn div(&self, other: &Ball) -> Option<Self> {
119 if other.lo() <= 0.0 && other.hi() >= 0.0 {
121 return None;
122 }
123 let mid = self.mid / other.mid;
124 let inv_b = 1.0 / other.mid;
128 let prop = (self.mid.abs() * other.rad * inv_b.abs() * inv_b.abs())
129 + self.rad * inv_b.abs()
130 + mid.abs() * f64::EPSILON * 2.0;
131 Some(Ball::new(mid, prop))
132 }
133
134 #[inline]
136 pub fn neg(&self) -> Self {
137 Ball::new(-self.mid, self.rad)
138 }
139
140 #[inline]
142 pub fn abs(&self) -> Self {
143 Ball::new(self.mid.abs(), self.rad)
144 }
145
146 pub fn sqrt(&self) -> Option<Self> {
149 if self.lo() < 0.0 {
150 return None;
151 }
152 let mid = self.mid.sqrt();
153 let lo_sqrt = self.lo().sqrt().max(f64::MIN_POSITIVE);
156 let prop = self.rad / (2.0 * lo_sqrt);
157 let fp_err = mid * f64::EPSILON * 2.0;
158 Some(Ball::new(mid, prop + fp_err))
159 }
160
161 pub fn powi(&self, n: i32) -> Self {
163 if n == 0 {
164 return Ball::from_exact(1.0);
165 }
166 if n == 1 {
167 return *self;
168 }
169 if n < 0 {
170 let pos = self.powi(-n);
172 return Ball::from_exact(1.0)
173 .div(&pos)
174 .unwrap_or(Ball::new(f64::NAN, f64::INFINITY));
175 }
176 let mut result = Ball::from_exact(1.0);
178 let mut base = *self;
179 let mut exp = n as u32;
180 while exp > 0 {
181 if exp & 1 == 1 {
182 result = result.mul(&base);
183 }
184 base = base.mul(&base);
185 exp >>= 1;
186 }
187 result
188 }
189}
190
191impl std::ops::Add for Ball {
192 type Output = Self;
193 fn add(self, rhs: Self) -> Self {
194 Ball::add(&self, &rhs)
195 }
196}
197
198impl std::ops::Sub for Ball {
199 type Output = Self;
200 fn sub(self, rhs: Self) -> Self {
201 Ball::sub(&self, &rhs)
202 }
203}
204
205impl std::ops::Mul for Ball {
206 type Output = Self;
207 fn mul(self, rhs: Self) -> Self {
208 Ball::mul(&self, &rhs)
209 }
210}
211
212impl std::ops::Neg for Ball {
213 type Output = Self;
214 fn neg(self) -> Self {
215 Ball::neg(&self)
216 }
217}
218
219pub fn ball_sin(x: Ball) -> Ball {
228 let mid_val = x.mid.sin();
229 let prop_err = x.rad;
231 let fp_err = (1.0_f64).max(mid_val.abs()) * f64::EPSILON * 4.0;
232 Ball::new(mid_val, prop_err + fp_err)
233}
234
235pub fn ball_cos(x: Ball) -> Ball {
237 let mid_val = x.mid.cos();
238 let prop_err = x.rad;
239 let fp_err = (1.0_f64).max(mid_val.abs()) * f64::EPSILON * 4.0;
240 Ball::new(mid_val, prop_err + fp_err)
241}
242
243pub fn ball_exp(x: Ball) -> Ball {
248 let mid_val = x.mid.exp();
249 let max_exp = (x.mid + x.rad).exp();
251 let prop_err = max_exp * x.rad;
252 let fp_err = mid_val * f64::EPSILON * 4.0;
253 Ball::new(mid_val, prop_err + fp_err)
254}
255
256pub fn ball_ln(x: Ball) -> Option<Ball> {
260 if x.lo() <= 0.0 {
261 return None;
262 }
263 let mid_val = x.mid.ln();
264 let prop_err = x.rad / x.lo();
266 let fp_err = mid_val.abs() * f64::EPSILON * 4.0;
267 Some(Ball::new(mid_val, prop_err + fp_err))
268}
269
270pub fn ball_gamma(x: Ball) -> Option<Ball> {
283 if x.lo() <= 0.0 {
284 return None;
285 }
286
287 let shift_target = 10.0_f64;
290 let shift = if x.mid < shift_target {
291 (shift_target - x.mid).ceil() as u32
292 } else {
293 0
294 };
295
296 let x_shifted = Ball::new(x.mid + shift as f64, x.rad);
299
300 let z = x_shifted.mid;
303 let ln_gamma_mid = (z - 0.5) * z.ln() - z + 0.5 * std::f64::consts::TAU.ln() + 1.0 / (12.0 * z)
304 - 1.0 / (360.0 * z * z * z)
305 + 1.0 / (1260.0 * z.powi(5));
306
307 let stirling_err = 1.0 / (1680.0 * z.powi(8));
310
311 let psi_bound = z.ln() + 1.0 / z;
314 let prop_err = psi_bound * x.rad;
315
316 let ln_gamma_rad = stirling_err + prop_err + z.ln() * f64::EPSILON * 4.0;
317
318 let gamma_shifted_mid = ln_gamma_mid.exp();
320 let gamma_shifted_rad = gamma_shifted_mid * ln_gamma_rad;
321 let mut gamma_ball = Ball::new(gamma_shifted_mid, gamma_shifted_rad);
322
323 for k in 0..shift {
325 let factor = Ball::new(x.mid + k as f64, x.rad);
326 gamma_ball = gamma_ball.div(&factor)?;
327 }
328
329 Some(gamma_ball)
330}
331
332#[inline]
340pub fn validate(computed: f64, ball: Ball) -> bool {
341 ball.contains(computed)
342}
343
344#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn test_ball_add_sub() {
354 let a = Ball::new(3.0, 0.1);
355 let b = Ball::new(2.0, 0.05);
356 let sum = a + b;
357 assert!(sum.contains(5.0));
358 assert!(sum.rad >= 0.15); let diff = a - b;
361 assert!(diff.contains(1.0));
362 assert!(diff.rad >= 0.15);
363 }
364
365 #[test]
366 fn test_ball_mul_propagation() {
367 let a = Ball::from_exact(2.0);
369 let b = Ball::from_exact(3.0);
370 let prod = a * b;
371 assert!(prod.contains(6.0));
372 assert!(prod.rad < 1e-12);
373
374 let a2 = Ball::new(2.0, 0.5);
376 let b2 = Ball::new(3.0, 0.5);
377 let prod2 = a2 * b2;
378 assert!(prod2.contains(6.0));
379 assert!(prod2.rad >= 2.5);
380 }
381
382 #[test]
383 fn test_ball_sin_contains() {
384 let x = Ball::new(std::f64::consts::PI / 6.0, 1e-8);
386 let s = ball_sin(x);
387 let true_val = (std::f64::consts::PI / 6.0).sin();
388 assert!(
389 s.contains(true_val),
390 "ball_sin should contain true sin value; ball=[{}, {}], true={}",
391 s.lo(),
392 s.hi(),
393 true_val
394 );
395 }
396
397 #[test]
398 fn test_ball_gamma_basic() {
399 let x = Ball::new(5.0, 1e-10);
401 let g = ball_gamma(x).expect("ball_gamma(5) should not be None");
402 assert!(
403 g.contains(24.0),
404 "ball_gamma(5) should contain 24.0; ball=[{}, {}]",
405 g.lo(),
406 g.hi()
407 );
408 }
409
410 #[test]
411 fn test_ball_gamma_one() {
412 let x = Ball::new(1.0, 1e-6);
415 let g = ball_gamma(x).expect("ball_gamma(1) should not be None");
416 assert!(
417 g.contains(1.0),
418 "ball_gamma(1) should contain 1.0; ball=[{}, {}]",
419 g.lo(),
420 g.hi()
421 );
422 }
423
424 #[test]
425 fn test_ball_validate() {
426 let b = Ball::new(2.71, 0.02);
427 assert!(validate(2.71, b)); assert!(validate(2.72, b)); assert!(validate(2.70, b)); assert!(!validate(2.68, b)); assert!(!validate(2.74, b)); }
433
434 #[test]
435 fn test_ball_div_by_zero() {
436 let a = Ball::new(5.0, 0.1);
438 let zero_ball = Ball::new(0.0, 1.0); assert!(a.div(&zero_ball).is_none());
440
441 let safe_div = Ball::new(2.0, 0.1);
443 let result = a.div(&safe_div);
444 assert!(result.is_some());
445 let r = result.expect("should divide safely");
446 assert!(r.contains(2.5));
447 }
448
449 #[test]
450 fn test_ball_sqrt() {
451 let four = Ball::new(4.0, 1e-10);
452 let root = four.sqrt().expect("sqrt(4) should not be None");
453 assert!(root.contains(2.0));
454
455 let neg = Ball::new(-1.0, 0.1);
457 assert!(neg.sqrt().is_none());
458 }
459
460 #[test]
461 fn test_ball_exp_contains() {
462 let x = Ball::new(1.0, 1e-9);
463 let e_ball = ball_exp(x);
464 assert!(e_ball.contains(std::f64::consts::E));
465 }
466
467 #[test]
468 fn test_ball_ln_contains() {
469 let x = Ball::new(std::f64::consts::E, 1e-10);
470 let ln_ball = ball_ln(x).expect("ln(e) should not be None");
471 assert!(ln_ball.contains(1.0));
472 }
473
474 #[test]
475 fn test_ball_powi() {
476 let x = Ball::new(2.0, 0.0);
477 let p = x.powi(10);
478 assert!(p.contains(1024.0));
479 }
480
481 #[test]
482 fn test_ball_from_interval() {
483 let b = Ball::from_interval(1.0, 3.0).expect("valid interval");
484 assert_eq!(b.mid, 2.0);
485 assert_eq!(b.rad, 1.0);
486 assert!(b.contains(1.0));
487 assert!(b.contains(3.0));
488
489 assert!(Ball::from_interval(3.0, 1.0).is_none());
491 }
492}