1use super::{Field, Ring, Scalar};
2use crate::geop_error::{GeopError, GeopResult};
3
4const F: u32 = 32;
6const SCALE: i128 = 1_i128 << F;
8
9const OV_LO: i64 = i64::MIN;
11const OV_HI: i64 = i64::MAX;
12
13#[inline]
17fn floor_div(n: i128, d: i128) -> i128 {
18 let q = n / d;
19 let r = n % d;
20 if r != 0 && (n ^ d) < 0 { q - 1 } else { q }
22}
23
24#[inline]
26fn ceil_div(n: i128, d: i128) -> i128 {
27 -floor_div(-n, d)
28}
29
30#[inline]
32fn sat_lo(v: i128) -> i64 {
33 if v <= i64::MIN as i128 {
34 OV_LO
35 } else if v >= i64::MAX as i128 {
36 OV_HI
37 } else {
38 v as i64
39 }
40}
41
42#[inline]
43fn sat_hi(v: i128) -> i64 {
44 sat_lo(v)
45}
46
47#[inline]
50fn next_up_f64(x: f64) -> f64 {
51 if x.is_nan() || x == f64::INFINITY {
52 return x;
53 }
54 if x == 0.0 {
55 return f64::MIN_POSITIVE;
56 }
57 let bits = x.to_bits();
58 f64::from_bits(if x > 0.0 { bits + 1 } else { bits - 1 })
59}
60
61#[inline]
62fn next_down_f64(x: f64) -> f64 {
63 if x.is_nan() || x == f64::NEG_INFINITY {
64 return x;
65 }
66 if x == 0.0 {
67 return -f64::MIN_POSITIVE;
68 }
69 let bits = x.to_bits();
70 f64::from_bits(if x < 0.0 { bits + 1 } else { bits - 1 })
71}
72
73#[inline]
77fn contains_periodic(lo: f64, hi: f64, target: f64, period: f64) -> bool {
78 let k = ((lo - target) / period).ceil();
79 target + k * period <= hi
80}
81
82fn interval_trig(lo: f64, hi: f64, f: impl Fn(f64) -> f64, max_at: f64, min_at: f64) -> (f64, f64) {
85 use std::f64::consts::TAU;
86 if !lo.is_finite() || !hi.is_finite() || hi - lo >= TAU {
87 return (-1.0, 1.0);
88 }
89 let (a, b) = (f(lo), f(hi));
90 let mut out_lo = next_down_f64(a.min(b));
91 let mut out_hi = next_up_f64(a.max(b));
92 if contains_periodic(lo, hi, max_at, TAU) {
93 out_hi = 1.0;
94 }
95 if contains_periodic(lo, hi, min_at, TAU) {
96 out_lo = -1.0;
97 }
98 (out_lo.max(-1.0), out_hi.min(1.0))
99}
100
101#[derive(Copy, Clone, PartialEq)]
105pub struct ScalInFPA64 {
106 pub lo: i64,
107 pub hi: i64,
108}
109
110impl ScalInFPA64 {
111 #[inline]
112 pub fn new(lo: i64, hi: i64) -> Self {
113 debug_assert!(lo <= hi, "ScalInFPA64::new: lo ({lo}) > hi ({hi})");
114 ScalInFPA64 { lo, hi }
115 }
116
117 #[inline]
118 pub fn degenerate(v: i64) -> Self {
119 ScalInFPA64 { lo: v, hi: v }
120 }
121
122 #[inline]
123 fn is_overflow(self) -> bool {
124 self.lo == OV_LO || self.hi == OV_HI
125 }
126
127 pub fn from_f64_outward(lo_f: f64, hi_f: f64) -> Self {
129 let lo_i = (lo_f * SCALE as f64).floor() as i128;
130 let hi_i = (hi_f * SCALE as f64).ceil() as i128;
131 ScalInFPA64::new(sat_lo(lo_i), sat_hi(hi_i))
132 }
133}
134
135impl core::fmt::Debug for ScalInFPA64 {
136 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
137 let lo_f = self.lo as f64 / SCALE as f64;
138 let hi_f = self.hi as f64 / SCALE as f64;
139 write!(f, "[{lo_f}, {hi_f}]")
140 }
141}
142
143impl core::fmt::Display for ScalInFPA64 {
144 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
145 let lo_f = self.lo as f64 / SCALE as f64;
146 let hi_f = self.hi as f64 / SCALE as f64;
147 write!(f, "{:.3}", (lo_f + hi_f) / 2.0)
148 }
149}
150
151const PI_LO: i64 = 13493037704;
157const PI_HI: i64 = 13493037705;
158const E_LO: i64 = 11674931555;
159const E_HI: i64 = 11674931556;
160
161impl Ring for ScalInFPA64 {
162 fn add(self, other: Self) -> Self {
163 let lo = self.lo.saturating_add(other.lo);
164 let hi = self.hi.saturating_add(other.hi);
165 ScalInFPA64::new(lo, hi)
166 }
167
168 fn sub(self, other: Self) -> Self {
169 let lo = self.lo.saturating_sub(other.hi);
170 let hi = self.hi.saturating_sub(other.lo);
171 ScalInFPA64::new(lo, hi)
172 }
173
174 fn mul(self, other: Self) -> Self {
175 let products = [
176 (self.lo as i128) * (other.lo as i128),
177 (self.lo as i128) * (other.hi as i128),
178 (self.hi as i128) * (other.lo as i128),
179 (self.hi as i128) * (other.hi as i128),
180 ];
181 let raw_lo = *products.iter().min().unwrap();
182 let raw_hi = *products.iter().max().unwrap();
183 let lo = floor_div(raw_lo, SCALE);
184 let hi = ceil_div(raw_hi, SCALE);
185 ScalInFPA64::new(sat_lo(lo), sat_hi(hi))
186 }
187
188 fn neg(self) -> Self {
189 ScalInFPA64::new(self.hi.saturating_neg(), self.lo.saturating_neg())
190 }
191}
192
193impl Field for ScalInFPA64 {
194 fn div(self, other: Self) -> GeopResult<Self> {
195 if other.lo <= 0 && other.hi >= 0 {
196 return Err(GeopError::new(
197 "ScalInFPA64::div: divisor interval contains zero",
198 ));
199 }
200 let a_lo = (self.lo as i128) << F;
201 let a_hi = (self.hi as i128) << F;
202 let b_lo = other.lo as i128;
203 let b_hi = other.hi as i128;
204
205 let candidates = [
206 floor_div(a_lo, b_hi),
207 floor_div(a_lo, b_lo),
208 floor_div(a_hi, b_hi),
209 floor_div(a_hi, b_lo),
210 ];
211 let candidates_hi = [
212 ceil_div(a_lo, b_hi),
213 ceil_div(a_lo, b_lo),
214 ceil_div(a_hi, b_hi),
215 ceil_div(a_hi, b_lo),
216 ];
217 let lo = *candidates.iter().min().unwrap();
218 let hi = *candidates_hi.iter().max().unwrap();
219 Ok(ScalInFPA64::new(sat_lo(lo), sat_hi(hi)))
220 }
221}
222
223impl Scalar for ScalInFPA64 {
226 const ZERO: Self = ScalInFPA64 { lo: 0, hi: 0 };
227 const ONE: Self = ScalInFPA64 {
228 lo: SCALE as i64,
229 hi: SCALE as i64,
230 };
231 const TWO: Self = ScalInFPA64 {
232 lo: 2 * SCALE as i64,
233 hi: 2 * SCALE as i64,
234 };
235 const PI: Self = ScalInFPA64 {
236 lo: PI_LO,
237 hi: PI_HI,
238 };
239 const E: Self = ScalInFPA64 { lo: E_LO, hi: E_HI };
240 const INFINITY: Self = ScalInFPA64 {
241 lo: OV_LO,
242 hi: OV_HI,
243 };
244 const ENTIRE: Self = ScalInFPA64 {
245 lo: i64::MIN,
246 hi: i64::MAX,
247 };
248
249 fn from_f64(v: f64) -> Self {
250 ScalInFPA64::from_f64_outward(v, v)
251 }
252 fn from_i64(v: i64) -> Self {
253 let shifted = (v as i128).checked_mul(SCALE);
254 match shifted {
255 Some(s) if s >= i64::MIN as i128 && s <= i64::MAX as i128 => {
256 ScalInFPA64::degenerate(s as i64)
257 }
258 _ => ScalInFPA64::INFINITY,
259 }
260 }
261
262 fn from_ratio(num: i64, den: i64) -> GeopResult<Self> {
263 if den == 0 {
264 return Err(GeopError::new(
265 "ScalInFPA64::from_ratio: denominator is zero",
266 ));
267 }
268 let n = (num as i128) << F;
269 let d = den as i128;
270 let lo = floor_div(n, d);
271 let hi = ceil_div(n, d);
272 Ok(ScalInFPA64::new(sat_lo(lo), sat_hi(hi)))
273 }
274
275 fn abs(self) -> Self {
276 if self.lo >= 0 {
277 self
278 } else if self.hi <= 0 {
279 ScalInFPA64::new(self.hi.saturating_neg(), self.lo.saturating_neg())
280 } else {
281 let hi = self.lo.saturating_neg().max(self.hi);
282 ScalInFPA64::new(0, hi)
283 }
284 }
285
286 fn sqrt(self) -> GeopResult<Self> {
287 if self.hi < 0 {
288 return Err(GeopError::new(
289 "ScalInFPA64::sqrt: interval is definitely negative",
290 ));
291 }
292 let lo_clamped = if self.lo < 0 { 0i64 } else { self.lo };
293 let lo_f = (lo_clamped as f64) / SCALE as f64;
295 let hi_f = (self.hi as f64) / SCALE as f64;
296 let sqrt_lo = next_down_f64(lo_f.sqrt());
297 let sqrt_hi = next_up_f64(hi_f.sqrt());
298 let lo_fixed = (sqrt_lo * SCALE as f64).floor() as i64;
300 let hi_fixed = (sqrt_hi * SCALE as f64).ceil() as i64;
301 Ok(ScalInFPA64::new(lo_fixed, hi_fixed))
302 }
303
304 fn sin(self) -> Self {
305 use std::f64::consts::FRAC_PI_2;
311 let (lo, hi) = interval_trig(
312 self.lo as f64 / SCALE as f64,
313 self.hi as f64 / SCALE as f64,
314 f64::sin,
315 FRAC_PI_2,
316 -FRAC_PI_2,
317 );
318 ScalInFPA64::from_f64_outward(lo, hi)
319 }
320
321 fn cos(self) -> Self {
322 use std::f64::consts::PI;
323 let (lo, hi) = interval_trig(
324 self.lo as f64 / SCALE as f64,
325 self.hi as f64 / SCALE as f64,
326 f64::cos,
327 0.0,
328 PI,
329 );
330 ScalInFPA64::from_f64_outward(lo, hi)
331 }
332
333 fn could_be_equal(self, other: Self) -> bool {
334 self.lo <= other.hi && other.lo <= self.hi
335 }
336
337 fn definitely_not_equal(self, other: Self) -> bool {
338 self.hi < other.lo || self.lo > other.hi
339 }
340
341 fn could_be_greater(self, other: Self) -> bool {
342 self.hi > other.lo
343 }
344
345 fn definitely_greater(self, other: Self) -> bool {
346 self.lo > other.hi
347 }
348
349 fn could_be_less(self, other: Self) -> bool {
350 self.lo < other.hi
351 }
352
353 fn definitely_less(self, other: Self) -> bool {
354 self.hi < other.lo
355 }
356
357 fn is_infinite(self) -> bool {
358 self.is_overflow()
359 }
360
361 fn is_finite(self) -> bool {
362 !self.is_overflow()
363 }
364
365 fn midpoint(self) -> Self {
366 let m = ((self.lo as i128 + self.hi as i128) / 2) as i64;
368 ScalInFPA64::degenerate(m)
369 }
370
371 fn is_sharp(self) -> bool {
372 self.lo == self.hi
373 }
374
375 fn lower(self) -> Self {
376 ScalInFPA64::degenerate(self.lo)
377 }
378
379 fn upper(self) -> Self {
380 ScalInFPA64::degenerate(self.hi)
381 }
382
383 fn width(self) -> Self {
384 let w = self.hi.saturating_sub(self.lo).max(0);
385 ScalInFPA64::new(w, w)
386 }
387
388 fn intersect(self, other: Self) -> Self {
389 let lo = self.lo.max(other.lo);
390 let hi = self.hi.min(other.hi);
391 if lo <= hi {
392 ScalInFPA64::new(lo, hi)
393 } else if self.hi.saturating_sub(self.lo) <= other.hi.saturating_sub(other.lo) {
394 self
395 } else {
396 other
397 }
398 }
399
400 fn to_f64(self) -> f64 {
401 let m = (self.lo as i128 + self.hi as i128) / 2;
402 m as f64 / SCALE as f64
403 }
404
405 fn union(self, other: Self) -> Self {
406 ScalInFPA64::new(self.lo.min(other.lo), self.hi.max(other.hi))
407 }
408
409 fn is_subset_of(self, other: Self) -> bool {
410 other.lo <= self.lo && self.hi <= other.hi
411 }
412}
413
414impl core::ops::Add for ScalInFPA64 {
415 type Output = Self;
416 fn add(self, rhs: Self) -> Self {
417 Ring::add(self, rhs)
418 }
419}
420impl core::ops::Sub for ScalInFPA64 {
421 type Output = Self;
422 fn sub(self, rhs: Self) -> Self {
423 Ring::sub(self, rhs)
424 }
425}
426impl core::ops::Mul for ScalInFPA64 {
427 type Output = Self;
428 fn mul(self, rhs: Self) -> Self {
429 Ring::mul(self, rhs)
430 }
431}
432impl core::ops::Neg for ScalInFPA64 {
433 type Output = Self;
434 fn neg(self) -> Self {
435 Ring::neg(self)
436 }
437}
438
439impl From<i64> for ScalInFPA64 {
440 fn from(v: i64) -> Self {
441 ScalInFPA64::from_i64(v)
442 }
443}
444
445impl From<f64> for ScalInFPA64 {
446 fn from(v: f64) -> Self {
447 ScalInFPA64::from_f64(v)
448 }
449}
450
451impl Default for ScalInFPA64 {
452 fn default() -> Self {
453 ScalInFPA64::ZERO
454 }
455}
456
457#[cfg(test)]
460mod tests {
461 use super::*;
462
463 fn pt(v: i64) -> ScalInFPA64 {
464 ScalInFPA64::from_i64(v)
465 }
466
467 fn iv(lo: i64, hi: i64) -> ScalInFPA64 {
468 ScalInFPA64::new(lo << F, hi << F)
469 }
470
471 #[test]
472 fn arithmetic_add() {
473 let r = pt(1).add(pt(2));
474 assert!(r.could_be_equal(pt(3)));
475 }
476
477 #[test]
478 fn arithmetic_sub() {
479 let r = pt(5).sub(pt(3));
480 assert!(r.could_be_equal(pt(2)));
481 }
482
483 #[test]
484 fn arithmetic_mul() {
485 let r = pt(3).mul(pt(4));
486 assert!(r.could_be_equal(pt(12)));
487 }
488
489 #[test]
490 fn arithmetic_div() {
491 let r = pt(10).div(pt(2)).unwrap();
492 assert!(r.could_be_equal(pt(5)));
493 }
494
495 #[test]
496 fn arithmetic_sqrt_4() {
497 let r = pt(4).sqrt().unwrap();
498 assert!(r.could_be_equal(pt(2)));
499 }
500
501 #[test]
502 fn arithmetic_sqrt_9() {
503 let r = pt(9).sqrt().unwrap();
504 assert!(r.could_be_equal(pt(3)));
505 }
506
507 #[test]
508 fn arithmetic_abs() {
509 assert!(pt(-3).abs().could_be_equal(pt(3)));
510 }
511
512 #[test]
513 fn arithmetic_neg() {
514 assert!(pt(-5).neg().could_be_equal(pt(5)));
515 }
516
517 #[test]
518 fn sin_at_a_point() {
519 assert!(pt(0).sin().could_be_equal(pt(0)));
520 }
521
522 #[test]
523 fn cos_at_a_point() {
524 assert!(pt(0).cos().could_be_equal(pt(1)));
525 }
526
527 #[test]
528 fn sin_over_a_peak_reaches_exactly_one() {
529 let lo = ScalInFPA64::from_f64(0.0);
530 let hi = ScalInFPA64::from_f64(std::f64::consts::PI);
531 let r = ScalInFPA64::new(lo.lo, hi.hi).sin();
532 assert!(r.hi == ScalInFPA64::ONE.hi && r.lo >= -1);
533 }
534
535 #[test]
536 fn err_div_by_zero() {
537 assert!(pt(1).div(ScalInFPA64::ZERO).is_err());
538 }
539
540 #[test]
541 fn err_sqrt_negative() {
542 assert!(pt(-1).sqrt().is_err());
543 }
544
545 #[test]
546 fn sqrt_straddles_zero_ok() {
547 let neg_one = -(1_i64 << F);
549 let four = 4_i64 << F;
550 let r = ScalInFPA64::new(neg_one, four).sqrt().unwrap();
551 assert!(r.could_be_equal(pt(2)));
552 }
553
554 #[test]
555 fn overflow_infinity() {
556 let big = ScalInFPA64::new(i64::MAX / 2, i64::MAX / 2);
557 let result = big.mul(big);
558 assert!(result.is_infinite());
559 }
560
561 #[test]
562 fn cmp_definitely_greater() {
563 assert!(pt(3).definitely_greater(pt(2)));
564 }
565
566 #[test]
567 fn cmp_could_be_less_false() {
568 assert!(!pt(3).definitely_less(pt(2)));
569 }
570
571 #[test]
572 fn cmp_overlapping_intervals_could_be_equal() {
573 assert!(iv(1, 3).could_be_equal(iv(2, 4)));
574 }
575
576 #[test]
577 fn cmp_disjoint_intervals_definitely_not_equal() {
578 assert!(iv(1, 2).definitely_not_equal(iv(3, 4)));
579 }
580
581 #[test]
582 fn entire_could_be_equal_point() {
583 assert!(ScalInFPA64::ENTIRE.could_be_equal(pt(42)));
584 assert!(pt(-1_000_000).could_be_equal(ScalInFPA64::ENTIRE));
585 }
586
587 #[test]
588 fn entire_could_be_equal_interval() {
589 assert!(ScalInFPA64::ENTIRE.could_be_equal(iv(-5, 5)));
590 }
591
592 #[test]
593 fn entire_could_be_greater_and_less() {
594 assert!(ScalInFPA64::ENTIRE.could_be_greater(pt(1_000_000)));
595 assert!(ScalInFPA64::ENTIRE.could_be_less(pt(-1_000_000)));
596 }
597
598 #[test]
599 fn entire_never_definitely() {
600 assert!(!ScalInFPA64::ENTIRE.definitely_not_equal(pt(0)));
601 assert!(!ScalInFPA64::ENTIRE.definitely_greater(pt(1_000_000)));
602 assert!(!ScalInFPA64::ENTIRE.definitely_less(pt(-1_000_000)));
603 }
604}