Skip to main content

deep_time/t_span/
arithmetic.rs

1use crate::{
2    ATTOS_PER_FS, ATTOS_PER_MS, ATTOS_PER_NS, ATTOS_PER_PS, ATTOS_PER_SEC, ATTOS_PER_SEC_I128,
3    ATTOS_PER_SECF, ATTOS_PER_US, Dt, Real, TSpan, floor_f,
4};
5
6impl TSpan {
7    #[inline]
8    pub const fn add(self, rhs: Self) -> Self {
9        let (sec, attos) = Self::add_time(self.sec, self.attos, rhs.sec, rhs.attos);
10        Self { sec, attos }
11    }
12
13    #[inline]
14    pub const fn sub(self, rhs: Self) -> Self {
15        let (sec, attos) = Self::sub_time(self.sec, self.attos, rhs.sec, rhs.attos);
16        Self { sec, attos }
17    }
18
19    /// Core saturating add for (sec, attos) pairs.
20    pub(crate) const fn add_time(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> (i64, u64) {
21        let mut sec = sec_a.saturating_add(sec_b);
22        let mut attos = sub_a as i64 + sub_b as i64;
23
24        if attos >= ATTOS_PER_SEC as i64 {
25            if sec < i64::MAX {
26                sec = sec.saturating_add(1);
27            }
28            attos -= ATTOS_PER_SEC as i64;
29        } else if attos < 0 {
30            if sec > i64::MIN {
31                sec = sec.saturating_sub(1);
32            }
33            attos += ATTOS_PER_SEC as i64;
34        }
35
36        let attos = if sec == i64::MAX {
37            ATTOS_PER_SEC - 1
38        } else if sec == i64::MIN {
39            0
40        } else {
41            attos as u64
42        };
43
44        (sec, attos)
45    }
46
47    /// Core saturating sub for (sec, attos) pairs.
48    pub(crate) const fn sub_time(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> (i64, u64) {
49        let mut sec = sec_a.saturating_sub(sec_b);
50        let mut attos = sub_a as i64 - sub_b as i64;
51
52        if attos < 0 {
53            if sec > i64::MIN {
54                sec = sec.saturating_sub(1);
55            }
56            attos += ATTOS_PER_SEC as i64;
57        } else if attos >= ATTOS_PER_SEC as i64 {
58            if sec < i64::MAX {
59                sec = sec.saturating_add(1);
60            }
61            attos -= ATTOS_PER_SEC as i64;
62        }
63
64        let attos = if sec == i64::MAX {
65            ATTOS_PER_SEC - 1
66        } else if sec == i64::MIN {
67            0
68        } else {
69            attos as u64
70        };
71
72        (sec, attos)
73    }
74
75    /// Returns `true` if this duration is exactly zero.
76    #[inline]
77    pub const fn is_zero(self) -> bool {
78        self.sec == 0 && self.attos == 0
79    }
80
81    /// Converts this duration to a floating-point number of seconds.
82    /// It computes `sec + attos / 10¹⁸` using `f64`.
83    /// It is lossy by design (f64 only has ~15.95 decimal digits of precision).
84    #[inline]
85    pub const fn to_sec_f(self) -> Real {
86        f!(self.sec) + f!(self.attos) / ATTOS_PER_SECF
87    }
88
89    /// Multiplies this duration by an integer scalar (exact).
90    ///
91    /// Uses 128-bit arithmetic internally.
92    pub const fn mul(self, rhs: i64) -> Self {
93        if rhs == 0 || self.is_zero() {
94            return Self::ZERO;
95        }
96        let total: i128 = self.to_attos().saturating_mul(rhs as i128);
97        Self::from_attos(total)
98    }
99
100    /// Divides this duration by an integer scalar (exact floor division).
101    ///
102    /// Returns `ZERO` if `rhs == 0`.
103    /// Uses floor division (toward negative infinity) for consistency
104    /// with the existing `floor` method.
105    pub const fn div(self, rhs: i64) -> Self {
106        if rhs == 0 || self.is_zero() {
107            return Self::ZERO;
108        }
109        let total = self.to_attos();
110        let result = total.div_euclid(rhs as i128);
111        Self::from_attos(result)
112    }
113
114    /// Returns the **largest** multiple of `unit` that is ≤ `self`.
115    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
116    pub const fn floor(self, unit: TSpan) -> TSpan {
117        if unit.is_zero() {
118            return self;
119        }
120        let a = self.to_attos();
121        let b = unit.to_attos();
122        let q = a.div_euclid(b);
123        let result = q.wrapping_mul(b);
124        Self::from_attos(result)
125    }
126
127    /// Returns the **smallest** multiple of `unit` that is ≥ `self`.
128    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
129    pub const fn ceil(self, unit: TSpan) -> TSpan {
130        if unit.is_zero() {
131            return self;
132        }
133        let a = self.to_attos();
134        let b = unit.to_attos();
135        // ceil(a/b) ≡ −floor(−a/b)
136        let neg_a = a.wrapping_neg();
137        let q = neg_a.div_euclid(b);
138        let q_ceil = q.wrapping_neg();
139        let result = q_ceil.wrapping_mul(b);
140        Self::from_attos(result)
141    }
142
143    /// Returns the nearest multiple of `unit`.
144    /// Halfway cases round **away from zero** (matches old `f64::round`).
145    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
146    pub const fn round(self, unit: TSpan) -> TSpan {
147        if unit.is_zero() {
148            return self;
149        }
150        let a = self.to_attos();
151        let b = unit.to_attos();
152
153        let q = a.div_euclid(b);
154        let r = a.rem_euclid(b);
155
156        // half = |b| / 2  (rounded up for tie-breaking away from zero)
157        let abs_b = b.wrapping_abs();
158        let two = 2i128;
159        let half = (abs_b + 1) / two;
160
161        if r >= half {
162            // round away from zero
163            let one = 1i128;
164            let q_rounded = if a < 0 { q - one } else { q + one };
165            let result = q_rounded.wrapping_mul(b);
166            Self::from_attos(result)
167        } else {
168            let result = q.wrapping_mul(b);
169            Self::from_attos(result)
170        }
171    }
172
173    /// Returns `floor(|self| / |unit|)` as `usize`, saturating at `usize::MAX`.
174    ///
175    /// Fully exact integer arithmetic using 128-bit intermediaries. Used by `TimeRange::len`.
176    pub const fn abs_div_floor(self, unit: TSpan) -> usize {
177        if unit.is_zero() {
178            return 0;
179        }
180        let a = self.to_attos().wrapping_abs();
181        let b = unit.to_attos().wrapping_abs();
182        let q = a.div_euclid(b);
183
184        if q > (usize::MAX as i128) {
185            usize::MAX
186        } else {
187            q as usize
188        }
189    }
190
191    /// - Integer part of `rhs` is multiplied **exactly** (pure i128 arithmetic).
192    /// - Fractional part (|frac| < 1) uses the 10¹⁵ scaling.
193    pub const fn mul_by_f(self, rhs: Real) -> Self {
194        if rhs.is_nan() {
195            return Self::ZERO;
196        }
197        if rhs.is_infinite() {
198            if self.is_zero() {
199                return Self::ZERO;
200            }
201            let self_pos = self.sec > 0 || (self.sec == 0 && self.attos != 0);
202            return if (rhs > 0.0) == self_pos {
203                Self::MAX
204            } else {
205                Self::MIN
206            };
207        }
208        if self.is_zero() || rhs == 0.0 {
209            return Self::ZERO;
210        }
211
212        let int_part = floor_f(rhs) as i128; // exact integer part
213        let frac_part = rhs - f!(int_part); // always in [0, 1)
214
215        // Integer part
216        let int_result = Self::from_attos(self.to_attos().saturating_mul(int_part));
217
218        // Fractional part: scaling is safe (|frac_part| < 1)
219        const SCALE: i128 = 1_000_000_000_000_000; // 10¹⁵
220        let frac_scaled = (frac_part * (SCALE as Real)) as i128;
221        let frac_product = self.to_attos().saturating_mul(frac_scaled);
222        let frac_attos = frac_product / SCALE;
223        let frac_result = Self::from_attos(frac_attos);
224
225        int_result.add(frac_result)
226    }
227
228    /// Divides by a real number (routes through the high-precision `mul_by_f`).
229    #[inline]
230    pub const fn div_by_f(self, rhs: Real) -> Self {
231        if rhs == 0.0 || rhs.is_nan() {
232            return if self.sec >= 0 { Self::MAX } else { Self::MIN };
233        }
234        self.mul_by_f(1.0 / rhs)
235    }
236
237    /// Divides this duration by 2 (convenience wrapper).
238    #[inline]
239    pub const fn div_by_2(self) -> TSpan {
240        self.div_by_f(2.0)
241    }
242
243    /// Adds exactly 1 second to this time value using saturating arithmetic.
244    #[inline]
245    pub const fn add_1sec(&mut self) {
246        self.sec = self.sec.saturating_add(1);
247    }
248
249    /// Adds exactly 1 minute (60 seconds) to this time value using saturating arithmetic.
250    #[inline]
251    pub const fn add_1min(&mut self) {
252        self.sec = self.sec.saturating_add(60);
253    }
254
255    /// Adds exactly 1 hour (3600 seconds) to this time value using saturating arithmetic.
256    #[inline]
257    pub const fn add_1hr(&mut self) {
258        self.sec = self.sec.saturating_add(3600);
259    }
260
261    /// Adds exactly 1 millisecond to this time value.
262    ///
263    /// This affects the subsecond component and may cause a carry into the seconds field.
264    #[inline]
265    pub const fn add_1ms(&mut self) {
266        TSpan::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_MS);
267    }
268
269    /// Adds exactly 1 microsecond to this time value.
270    ///
271    /// This affects the subsecond component and may cause a carry into the seconds field.
272    #[inline]
273    pub const fn add_1us(&mut self) {
274        TSpan::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_US);
275    }
276
277    /// Adds exactly 1 nanosecond to this time value.
278    ///
279    /// This affects the subsecond component and may cause a carry into the seconds field.
280    #[inline]
281    pub const fn add_1ns(&mut self) {
282        TSpan::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_NS);
283    }
284
285    /// Adds the specified number of seconds to this time value using saturating arithmetic.
286    #[inline]
287    pub const fn add_sec(&mut self, n: i64) {
288        self.sec = self.sec.saturating_add(n);
289    }
290
291    /// Adds the specified number of minutes to this time value using saturating arithmetic.
292    #[inline]
293    pub const fn add_min(&mut self, n: i64) {
294        self.sec = self.sec.saturating_add(n.saturating_mul(60));
295    }
296
297    /// Adds the specified number of hours to this time value using saturating arithmetic.
298    #[inline]
299    pub const fn add_hr(&mut self, n: i64) {
300        self.sec = self.sec.saturating_add(n.saturating_mul(3600));
301    }
302
303    /// Adds the specified number of milliseconds to this time value.
304    ///
305    /// Handles carry into the seconds field using saturating logic.
306    #[inline]
307    pub const fn add_ms(&mut self, n: i64) {
308        TSpan::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_MS);
309    }
310
311    /// Adds the specified number of microseconds to this time value.
312    ///
313    /// Handles carry into the seconds field using saturating logic.
314    #[inline]
315    pub const fn add_us(&mut self, n: i64) {
316        TSpan::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_US);
317    }
318
319    /// Adds the specified number of nanoseconds to this time value.
320    ///
321    /// Handles carry into the seconds field using saturating logic.
322    #[inline]
323    pub const fn add_ns(&mut self, n: i64) {
324        TSpan::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_NS);
325    }
326
327    /// Adds the specified number of picoseconds to this time value.
328    ///
329    /// Handles carry into the seconds field using saturating logic.
330    #[inline]
331    pub const fn add_ps(&mut self, n: i64) {
332        TSpan::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_PS);
333    }
334
335    /// Adds the specified number of femtoseconds to this time value.
336    ///
337    /// Handles carry into the seconds field using saturating logic.
338    #[inline]
339    pub const fn add_fs(&mut self, n: i64) {
340        TSpan::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_FS);
341    }
342
343    /// Adds the specified number of attoseconds to this time value.
344    ///
345    /// Handles carry into the seconds field using saturating logic.
346    #[inline]
347    pub const fn add_attos(&mut self, n: i64) {
348        TSpan::add_attos_span(&mut self.sec, &mut self.attos, n, 1);
349    }
350
351    /// Subtracts exactly 1 hour (3600 seconds) from this time value using saturating arithmetic.
352    #[inline]
353    pub const fn sub_1hr(&mut self) {
354        self.sec = self.sec.saturating_sub(3600);
355    }
356
357    /// Subtracts exactly 1 minute (60 seconds) from this time value using saturating arithmetic.
358    #[inline]
359    pub const fn sub_1min(&mut self) {
360        self.sec = self.sec.saturating_sub(60);
361    }
362
363    /// Subtracts exactly 1 second from this time value using saturating arithmetic.
364    #[inline]
365    pub const fn sub_1sec(&mut self) {
366        self.sec = self.sec.saturating_sub(1);
367    }
368
369    /// Subtracts exactly 1 millisecond from this time value.
370    ///
371    /// This affects the subsecond component and may cause a borrow from the seconds field.
372    #[inline]
373    pub const fn sub_1ms(&mut self) {
374        TSpan::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_MS);
375    }
376
377    /// Subtracts exactly 1 microsecond from this time value.
378    ///
379    /// This affects the subsecond component and may cause a borrow from the seconds field.
380    #[inline]
381    pub const fn sub_1us(&mut self) {
382        TSpan::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_US);
383    }
384
385    /// Subtracts exactly 1 nanosecond from this time value.
386    ///
387    /// This affects the subsecond component and may cause a borrow from the seconds field.
388    #[inline]
389    pub const fn sub_1ns(&mut self) {
390        TSpan::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_NS);
391    }
392
393    /// Subtracts the specified number of seconds from this time value using saturating arithmetic.
394    #[inline]
395    pub const fn sub_sec(&mut self, n: i64) {
396        self.sec = self.sec.saturating_sub(n);
397    }
398
399    /// Subtracts the specified number of minutes from this time value using saturating arithmetic.
400    #[inline]
401    pub const fn sub_min(&mut self, n: i64) {
402        self.sec = self.sec.saturating_sub(n.saturating_mul(60));
403    }
404
405    /// Subtracts the specified number of hours from this time value using saturating arithmetic.
406    #[inline]
407    pub const fn sub_hr(&mut self, n: i64) {
408        self.sec = self.sec.saturating_sub(n.saturating_mul(3600));
409    }
410
411    /// Subtracts the specified number of milliseconds from this time value.
412    ///
413    /// Handles borrow from the seconds field using saturating logic.
414    #[inline]
415    pub const fn sub_ms(&mut self, n: i64) {
416        TSpan::add_attos_span(
417            &mut self.sec,
418            &mut self.attos,
419            n.saturating_neg(),
420            ATTOS_PER_MS,
421        );
422    }
423
424    /// Subtracts the specified number of microseconds from this time value.
425    ///
426    /// Handles borrow from the seconds field using saturating logic.
427    #[inline]
428    pub const fn sub_us(&mut self, n: i64) {
429        TSpan::add_attos_span(
430            &mut self.sec,
431            &mut self.attos,
432            n.saturating_neg(),
433            ATTOS_PER_US,
434        );
435    }
436
437    /// Subtracts the specified number of nanoseconds from this time value.
438    ///
439    /// Handles borrow from the seconds field using saturating logic.
440    #[inline]
441    pub const fn sub_ns(&mut self, n: i64) {
442        TSpan::add_attos_span(
443            &mut self.sec,
444            &mut self.attos,
445            n.saturating_neg(),
446            ATTOS_PER_NS,
447        );
448    }
449
450    /// Subtracts the specified number of picoseconds from this time value.
451    ///
452    /// Handles borrow from the seconds field using saturating logic.
453    #[inline]
454    pub const fn sub_ps(&mut self, n: i64) {
455        TSpan::add_attos_span(
456            &mut self.sec,
457            &mut self.attos,
458            n.saturating_neg(),
459            ATTOS_PER_PS,
460        );
461    }
462
463    /// Subtracts the specified number of femtoseconds from this time value.
464    ///
465    /// Handles borrow from the seconds field using saturating logic.
466    #[inline]
467    pub const fn sub_fs(&mut self, n: i64) {
468        TSpan::add_attos_span(
469            &mut self.sec,
470            &mut self.attos,
471            n.saturating_neg(),
472            ATTOS_PER_FS,
473        );
474    }
475
476    /// Subtracts the specified number of attoseconds from this time value.
477    ///
478    /// Handles borrow from the seconds field using saturating logic.
479    #[inline]
480    pub const fn sub_attos(&mut self, n: i64) {
481        TSpan::add_attos_span(&mut self.sec, &mut self.attos, n.saturating_neg(), 1);
482    }
483
484    /// Internal helper used by add_1ms / add_1us / add_1ns.
485    #[doc(hidden)]
486    pub(crate) const fn add_attos_to(sec: &mut i64, attos: &mut u64, amount: u64) {
487        let total = *attos + amount;
488        let carry_sec = total / ATTOS_PER_SEC;
489        *attos = total % ATTOS_PER_SEC;
490        *sec = sec.saturating_add(carry_sec as i64);
491    }
492
493    /// Internal method to add or subtract a subsecond span in a given unit.
494    ///
495    /// This is the core implementation for all subsecond addition and subtraction
496    /// operations. It properly handles carry and borrow between the fractional
497    /// part (`attos`) and the whole seconds (`sec`), using saturating arithmetic
498    /// throughout.
499    #[doc(hidden)]
500    pub(crate) const fn add_attos_span(sec: &mut i64, attos: &mut u64, n: i64, unit: u64) {
501        if n == 0 {
502            return;
503        }
504
505        let mps = ATTOS_PER_SEC;
506
507        if n >= 0 {
508            let amount = (n as u64).saturating_mul(unit);
509            let total = attos.saturating_add(amount);
510
511            let carry = total / mps;
512            let new_frac = total % mps;
513
514            *sec = sec.saturating_add(carry as i64);
515            *attos = new_frac;
516        } else {
517            let amount = n.unsigned_abs().saturating_mul(unit);
518            let borrow_sec = amount / mps;
519            let borrow_frac = amount % mps;
520
521            *sec = sec.saturating_sub(borrow_sec as i64);
522
523            if *attos >= borrow_frac {
524                *attos -= borrow_frac;
525            } else {
526                *attos += mps - borrow_frac;
527                *sec = sec.saturating_sub(1);
528            }
529        }
530
531        // Final saturation clamp
532        if *sec == i64::MAX {
533            *attos = mps - 1;
534        } else if *sec == i64::MIN {
535            *attos = 0;
536        }
537    }
538
539    /// Total attoseconds (exact i128 representation within the representable range).
540    #[inline]
541    pub const fn to_attos(self) -> i128 {
542        (self.sec as i128) * ATTOS_PER_SEC_I128 + (self.attos as i128)
543    }
544
545    /// Returns the total duration in seconds.
546    #[inline]
547    pub const fn to_sec(&mut self) -> i64 {
548        self.carry_over();
549        self.sec
550    }
551
552    /// Returns the total duration in milliseconds.
553    #[inline]
554    pub const fn to_ms(self) -> i128 {
555        self.to_attos() / (ATTOS_PER_MS as i128)
556    }
557
558    /// Returns the total duration in microseconds.
559    #[inline]
560    pub const fn to_us(self) -> i128 {
561        self.to_attos() / (ATTOS_PER_US as i128)
562    }
563
564    /// Returns the total duration in nanoseconds.
565    #[inline]
566    pub const fn to_ns(self) -> i128 {
567        self.to_attos() / (ATTOS_PER_NS as i128)
568    }
569
570    /// Returns the total duration in picoseconds.
571    #[inline]
572    pub const fn to_ps(self) -> i128 {
573        self.to_attos() / (ATTOS_PER_PS as i128)
574    }
575
576    /// Returns the total duration in femtoseconds.
577    #[inline]
578    pub const fn to_fs(self) -> i128 {
579        self.to_attos() / (ATTOS_PER_FS as i128)
580    }
581
582    /// Returns `self - rhs` exactly.
583    ///
584    /// This is the normal case when subtracting two durations.
585    #[inline]
586    pub const fn to_diff(self, rhs: Self) -> Self {
587        Self::diff_raw(self.sec, self.attos, rhs.sec, rhs.attos)
588    }
589
590    /// Returns `self - rhs` exactly, where `rhs` is a `Dt`.
591    #[inline]
592    pub const fn to_diff_tp(self, rhs: Dt) -> Self {
593        Self::diff_raw(self.sec, self.attos, rhs.sec, rhs.attos)
594    }
595
596    #[inline]
597    pub(crate) const fn diff_raw(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> Self {
598        if sub_a >= sub_b {
599            Self {
600                sec: sec_a.saturating_sub(sec_b),
601                attos: sub_a - sub_b,
602            }
603        } else {
604            Self {
605                sec: sec_a.saturating_sub(sec_b).saturating_sub(1),
606                attos: sub_a.saturating_add(ATTOS_PER_SEC.saturating_sub(sub_b)),
607            }
608        }
609    }
610}