Skip to main content

deep_time/dt/
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, Drift, Dt, Real, Scale, Spacetime, floor_f,
4};
5
6impl Dt {
7    #[inline]
8    pub const fn add(self, span: Dt) -> Self {
9        if !span.is_zero() {
10            let (sec, attos) = Dt::add_time(self.sec, self.attos, span.sec, span.attos);
11            Self { sec, attos }
12        } else {
13            self
14        }
15    }
16
17    #[inline]
18    pub const fn sub(self, span: Dt) -> Self {
19        if !span.is_zero() {
20            let (sec, attos) = Dt::sub_time(self.sec, self.attos, span.sec, span.attos);
21            Self { sec, attos }
22        } else {
23            self
24        }
25    }
26
27    /// Converts this `Dt` to a floating-point number of seconds since the reference epoch of its associated scale.
28    /// - The conversion is lossy, as [`Real`] provides approximately 15.95 decimal digits of precision.
29    pub const fn to_sec_f(&self) -> Real {
30        let Dt { sec, attos: rem } = self.carry_over();
31
32        if sec < 0 && rem > ATTOS_PER_SEC / 2 {
33            // Rewrite to avoid cancellation:
34            // sec + rem/ATTOS_PER_SEC  ==  (sec + 1) - (ATTOS_PER_SEC - rem)/ATTOS_PER_SEC
35            // The right-hand side has no large opposing terms.
36            let small = ATTOS_PER_SEC - rem; // positive and now small-ish
37            let small_f = f!(small) / ATTOS_PER_SECF;
38            (f!(sec) + 1.0) - small_f
39        } else {
40            // Normal path (no problematic cancellation)
41            f!(sec) + f!(rem) / ATTOS_PER_SECF
42        }
43    }
44
45    /// Advances this `Dt` by the given elapsed duration while applying the relativistic proper-time correction
46    /// derived from the supplied `Spacetime` model.
47    ///
48    /// This method is intended for simulation of remote clocks (e.g., Earth time as observed from a spacecraft).
49    /// For the spacecraft's own hardware proper-time clock, use the plain `add` method instead.
50    #[inline]
51    pub const fn adjusted_advance(&mut self, elapsed: &Dt, spacetime: &Spacetime) {
52        let dtau = elapsed.add(Drift::from_spacetime(spacetime).time_diff_after(elapsed));
53        *self = self.add(dtau);
54    }
55
56    /// Advances this `Dt` by the given elapsed duration while applying the relativistic proper-time correction
57    /// from a pre-computed `Drift` value.
58    ///
59    /// This is an optimized variant of `adjusted_advance` for callers that already hold a `Drift` instance.
60    /// It is intended for simulation of remote clocks; the spacecraft's own hardware clock should use the plain `add` method.
61    #[inline]
62    pub const fn adjusted_advance_using_drift(&mut self, elapsed: &Dt, drift: &Drift) {
63        let dtau = elapsed.add(drift.time_diff_after(elapsed));
64        *self = self.add(dtau);
65    }
66
67    /// Computes the signed duration between this `Dt` and another `Dt`.
68    #[inline]
69    pub const fn to_diff_raw(&self, other: Self) -> Dt {
70        Self::diff_raw_internal(self.sec, self.attos, other.sec, other.attos)
71    }
72
73    /// Computes the signed duration between this `Dt` and another `Dt` as a float.
74    #[inline]
75    pub const fn to_diff_raw_f(&self, other: Self) -> Real {
76        self.to_sec_f() - other.to_sec_f()
77    }
78
79    /// Adds exactly 1 second to this time value using saturating arithmetic.
80    #[inline]
81    pub const fn add_1sec(&mut self) {
82        self.sec = self.sec.saturating_add(1);
83    }
84
85    /// Adds exactly 1 minute (60 seconds) to this time value using saturating arithmetic.
86    #[inline]
87    pub const fn add_1min(&mut self) {
88        self.sec = self.sec.saturating_add(60);
89    }
90
91    /// Adds exactly 1 hour (3600 seconds) to this time value using saturating arithmetic.
92    #[inline]
93    pub const fn add_1hr(&mut self) {
94        self.sec = self.sec.saturating_add(3600);
95    }
96
97    /// Adds exactly 1 millisecond to this time value.
98    ///
99    /// This affects the subsecond component and may cause a carry into the seconds field.
100    #[inline]
101    pub const fn add_1ms(&mut self) {
102        Self::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_MS);
103    }
104
105    /// Adds exactly 1 microsecond to this time value.
106    ///
107    /// This affects the subsecond component and may cause a carry into the seconds field.
108    #[inline]
109    pub const fn add_1us(&mut self) {
110        Self::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_US);
111    }
112
113    /// Adds exactly 1 nanosecond to this time value.
114    ///
115    /// This affects the subsecond component and may cause a carry into the seconds field.
116    #[inline]
117    pub const fn add_1ns(&mut self) {
118        Self::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_NS);
119    }
120
121    /// Adds the specified number of seconds to this time value using saturating arithmetic.
122    #[inline]
123    pub const fn add_sec(&mut self, n: i64) {
124        self.sec = self.sec.saturating_add(n);
125    }
126
127    /// Adds the specified number of minutes to this time value using saturating arithmetic.
128    #[inline]
129    pub const fn add_min(&mut self, n: i64) {
130        self.sec = self.sec.saturating_add(n.saturating_mul(60));
131    }
132
133    /// Adds the specified number of hours to this time value using saturating arithmetic.
134    #[inline]
135    pub const fn add_hr(&mut self, n: i64) {
136        self.sec = self.sec.saturating_add(n.saturating_mul(3600));
137    }
138
139    /// Adds the specified number of milliseconds to this time value.
140    ///
141    /// Handles carry into the seconds field using saturating logic.
142    #[inline]
143    pub const fn add_ms(&mut self, n: i64) {
144        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_MS);
145    }
146
147    /// Adds the specified number of microseconds to this time value.
148    ///
149    /// Handles carry into the seconds field using saturating logic.
150    #[inline]
151    pub const fn add_us(&mut self, n: i64) {
152        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_US);
153    }
154
155    /// Adds the specified number of nanoseconds to this time value.
156    ///
157    /// Handles carry into the seconds field using saturating logic.
158    #[inline]
159    pub const fn add_ns(&mut self, n: i64) {
160        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_NS);
161    }
162
163    /// Adds the specified number of picoseconds to this time value.
164    ///
165    /// Handles carry into the seconds field using saturating logic.
166    #[inline]
167    pub const fn add_ps(&mut self, n: i64) {
168        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_PS);
169    }
170
171    /// Adds the specified number of femtoseconds to this time value.
172    ///
173    /// Handles carry into the seconds field using saturating logic.
174    #[inline]
175    pub const fn add_fs(&mut self, n: i64) {
176        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_FS);
177    }
178
179    /// Adds the specified number of attoseconds to this time value.
180    ///
181    /// Handles carry into the seconds field using saturating logic.
182    #[inline]
183    pub const fn add_attos(&mut self, n: i64) {
184        Self::add_attos_span(&mut self.sec, &mut self.attos, n, 1);
185    }
186
187    /// Subtracts exactly 1 hour (3600 seconds) from this time value using saturating arithmetic.
188    #[inline]
189    pub const fn sub_1hr(&mut self) {
190        self.sec = self.sec.saturating_sub(3600);
191    }
192
193    /// Subtracts exactly 1 minute (60 seconds) from this time value using saturating arithmetic.
194    #[inline]
195    pub const fn sub_1min(&mut self) {
196        self.sec = self.sec.saturating_sub(60);
197    }
198
199    /// Subtracts exactly 1 second from this time value using saturating arithmetic.
200    #[inline]
201    pub const fn sub_1sec(&mut self) {
202        self.sec = self.sec.saturating_sub(1);
203    }
204
205    /// Subtracts exactly 1 millisecond from this time value.
206    ///
207    /// This affects the subsecond component and may cause a borrow from the seconds field.
208    #[inline]
209    pub const fn sub_1ms(&mut self) {
210        Self::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_MS);
211    }
212
213    /// Subtracts exactly 1 microsecond from this time value.
214    ///
215    /// This affects the subsecond component and may cause a borrow from the seconds field.
216    #[inline]
217    pub const fn sub_1us(&mut self) {
218        Self::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_US);
219    }
220
221    /// Subtracts exactly 1 nanosecond from this time value.
222    ///
223    /// This affects the subsecond component and may cause a borrow from the seconds field.
224    #[inline]
225    pub const fn sub_1ns(&mut self) {
226        Self::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_NS);
227    }
228
229    /// Subtracts the specified number of seconds from this time value using saturating arithmetic.
230    #[inline]
231    pub const fn sub_sec(&mut self, n: i64) {
232        self.sec = self.sec.saturating_sub(n);
233    }
234
235    /// Subtracts the specified number of minutes from this time value using saturating arithmetic.
236    #[inline]
237    pub const fn sub_min(&mut self, n: i64) {
238        self.sec = self.sec.saturating_sub(n.saturating_mul(60));
239    }
240
241    /// Subtracts the specified number of hours from this time value using saturating arithmetic.
242    #[inline]
243    pub const fn sub_hr(&mut self, n: i64) {
244        self.sec = self.sec.saturating_sub(n.saturating_mul(3600));
245    }
246
247    /// Subtracts the specified number of milliseconds from this time value.
248    ///
249    /// Handles borrow from the seconds field using saturating logic.
250    #[inline]
251    pub const fn sub_ms(&mut self, n: i64) {
252        Self::add_attos_span(
253            &mut self.sec,
254            &mut self.attos,
255            n.saturating_neg(),
256            ATTOS_PER_MS,
257        );
258    }
259
260    /// Subtracts the specified number of microseconds from this time value.
261    ///
262    /// Handles borrow from the seconds field using saturating logic.
263    #[inline]
264    pub const fn sub_us(&mut self, n: i64) {
265        Self::add_attos_span(
266            &mut self.sec,
267            &mut self.attos,
268            n.saturating_neg(),
269            ATTOS_PER_US,
270        );
271    }
272
273    /// Subtracts the specified number of nanoseconds from this time value.
274    ///
275    /// Handles borrow from the seconds field using saturating logic.
276    #[inline]
277    pub const fn sub_ns(&mut self, n: i64) {
278        Self::add_attos_span(
279            &mut self.sec,
280            &mut self.attos,
281            n.saturating_neg(),
282            ATTOS_PER_NS,
283        );
284    }
285
286    /// Subtracts the specified number of picoseconds from this time value.
287    ///
288    /// Handles borrow from the seconds field using saturating logic.
289    #[inline]
290    pub const fn sub_ps(&mut self, n: i64) {
291        Self::add_attos_span(
292            &mut self.sec,
293            &mut self.attos,
294            n.saturating_neg(),
295            ATTOS_PER_PS,
296        );
297    }
298
299    /// Subtracts the specified number of femtoseconds from this time value.
300    ///
301    /// Handles borrow from the seconds field using saturating logic.
302    #[inline]
303    pub const fn sub_fs(&mut self, n: i64) {
304        Self::add_attos_span(
305            &mut self.sec,
306            &mut self.attos,
307            n.saturating_neg(),
308            ATTOS_PER_FS,
309        );
310    }
311
312    /// Subtracts the specified number of attoseconds from this time value.
313    ///
314    /// Handles borrow from the seconds field using saturating logic.
315    #[inline]
316    pub const fn sub_attos(&mut self, n: i64) {
317        Self::add_attos_span(&mut self.sec, &mut self.attos, n.saturating_neg(), 1);
318    }
319
320    /// Total attoseconds (exact i128 representation within the representable range).
321    #[inline]
322    pub const fn to_attos(&self) -> i128 {
323        (self.sec as i128) * ATTOS_PER_SEC_I128 + (self.attos as i128)
324    }
325
326    /// Returns the total time in milliseconds.
327    #[inline]
328    pub const fn to_ms(&self) -> i128 {
329        self.to_attos() / (ATTOS_PER_MS as i128)
330    }
331
332    /// Returns the total time in microseconds.
333    #[inline]
334    pub const fn to_us(&self) -> i128 {
335        self.to_attos() / (ATTOS_PER_US as i128)
336    }
337
338    /// Returns the total time in nanoseconds.
339    #[inline]
340    pub const fn to_ns(&self) -> i128 {
341        self.to_attos() / (ATTOS_PER_NS as i128)
342    }
343
344    /// Returns the total time in picoseconds.
345    #[inline]
346    pub const fn to_ps(&self) -> i128 {
347        self.to_attos() / (ATTOS_PER_PS as i128)
348    }
349
350    /// Returns the total time in femtoseconds.
351    #[inline]
352    pub const fn to_fs(&self) -> i128 {
353        self.to_attos() / (ATTOS_PER_FS as i128)
354    }
355
356    /// Core saturating add for (sec, attos) pairs.
357    pub(crate) const fn add_time(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> (i64, u64) {
358        let mut sec = sec_a.saturating_add(sec_b);
359        let mut attos = sub_a as i64 + sub_b as i64;
360
361        if attos >= ATTOS_PER_SEC as i64 {
362            if sec < i64::MAX {
363                sec = sec.saturating_add(1);
364            }
365            attos -= ATTOS_PER_SEC as i64;
366        } else if attos < 0 {
367            if sec > i64::MIN {
368                sec = sec.saturating_sub(1);
369            }
370            attos += ATTOS_PER_SEC as i64;
371        }
372
373        let attos = if sec == i64::MAX {
374            ATTOS_PER_SEC - 1
375        } else if sec == i64::MIN {
376            0
377        } else {
378            attos as u64
379        };
380
381        (sec, attos)
382    }
383
384    /// Core saturating sub for (sec, attos) pairs.
385    pub(crate) const fn sub_time(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> (i64, u64) {
386        let mut sec = sec_a.saturating_sub(sec_b);
387        let mut attos = sub_a as i64 - sub_b as i64;
388
389        if attos < 0 {
390            if sec > i64::MIN {
391                sec = sec.saturating_sub(1);
392            }
393            attos += ATTOS_PER_SEC as i64;
394        } else if attos >= ATTOS_PER_SEC as i64 {
395            if sec < i64::MAX {
396                sec = sec.saturating_add(1);
397            }
398            attos -= ATTOS_PER_SEC as i64;
399        }
400
401        let attos = if sec == i64::MAX {
402            ATTOS_PER_SEC - 1
403        } else if sec == i64::MIN {
404            0
405        } else {
406            attos as u64
407        };
408
409        (sec, attos)
410    }
411
412    /// Returns `true` if this time is exactly zero.
413    #[inline]
414    pub const fn is_zero(&self) -> bool {
415        self.sec == 0 && self.attos == 0
416    }
417
418    /// Returns `true` if this time is strictly positive **(> 0)**.
419    #[inline]
420    pub const fn is_positive(&self) -> bool {
421        if self.sec > 0 {
422            true
423        } else if self.sec == 0 {
424            self.attos != 0
425        } else {
426            let k = (-self.sec) as u64;
427            let quot = self.attos / ATTOS_PER_SEC;
428            let rem = self.attos % ATTOS_PER_SEC;
429
430            quot > k || (quot == k && rem > 0)
431        }
432    }
433
434    /// Multiplies this time by an integer scalar (exact).
435    ///
436    /// Uses 128-bit arithmetic internally.
437    pub const fn mul(self, rhs: i64) -> Self {
438        if rhs == 0 || self.is_zero() {
439            return Self::ZERO;
440        }
441        let total: i128 = self.to_attos().saturating_mul(rhs as i128);
442        Self::from_attos(total, Scale::TAI)
443    }
444
445    /// Divides this time by an integer scalar (exact floor division).
446    ///
447    /// Returns `ZERO` if `rhs == 0`.
448    /// Uses floor division (toward negative infinity) for consistency
449    /// with the existing `floor` method.
450    pub const fn div(self, rhs: i64) -> Self {
451        if rhs == 0 || self.is_zero() {
452            return Self::ZERO;
453        }
454        let total = self.to_attos();
455        let result = safe_div_euc!(total, rhs as i128, 0i128);
456        Self::from_attos(result, Scale::TAI)
457    }
458
459    /// Returns the **largest** multiple of `unit` that is ≤ `self`.
460    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
461    pub const fn floor(&self, unit: Self) -> Self {
462        if unit.is_zero() {
463            return *self;
464        }
465        let a = self.to_attos();
466        let b = unit.to_attos();
467        let q = safe_div_euc!(a, b, 0i128);
468        let result = q.wrapping_mul(b);
469        Self::from_attos(result, Scale::TAI)
470    }
471
472    /// Returns the **smallest** multiple of `unit` that is ≥ `self`.
473    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
474    pub const fn ceil(&self, unit: Self) -> Self {
475        if unit.is_zero() {
476            return *self;
477        }
478        let a = self.to_attos();
479        let b = unit.to_attos();
480        // ceil(a/b) ≡ −floor(−a/b)
481        let neg_a = a.wrapping_neg();
482        let q = safe_div_euc!(neg_a, b, 0i128);
483        let q_ceil = q.wrapping_neg();
484        let result = q_ceil.wrapping_mul(b);
485        Self::from_attos(result, Scale::TAI)
486    }
487
488    /// Returns the nearest multiple of `unit`.
489    ///
490    /// Halfway cases round **away from zero** (e.g. `2.5 → 3.0`, `-2.5 → -3.0`),
491    /// matching the behavior of the old `f64::round()`.
492    ///
493    /// - If `unit` is zero, returns `self` unchanged (preserves full precision).
494    /// - Uses Euclidean division internally for correct behavior on negative values.
495    /// - The result is always a multiple of `unit`.
496    pub const fn round(&self, unit: Self) -> Self {
497        if unit.is_zero() {
498            return *self;
499        }
500
501        let a = self.to_attos();
502        let b = unit.to_attos();
503
504        let abs_a = a.wrapping_abs();
505        let abs_b = b.wrapping_abs();
506
507        let q = safe_div_euc!(abs_a, abs_b, 0i128);
508        let r = safe_rem_euc!(abs_a, abs_b, 0i128);
509
510        let half = (abs_b + 1) / 2;
511
512        let q_rounded = if r >= half { q + 1 } else { q };
513
514        let rounded_abs = q_rounded.wrapping_mul(abs_b);
515
516        let result = if a < 0 { -rounded_abs } else { rounded_abs };
517
518        Self::from_attos(result, Scale::TAI)
519    }
520
521    /// Returns `floor(|self| / |unit|)` as `usize`, saturating at `usize::MAX`.
522    ///
523    /// Fully exact integer arithmetic using 128-bit intermediaries. Used by `TimeRange::len`.
524    pub const fn abs_div_floor(&self, unit: Self) -> usize {
525        if unit.is_zero() {
526            return 0;
527        }
528        let a = self.to_attos().wrapping_abs();
529        let b = unit.to_attos().wrapping_abs();
530        let q = safe_div_euc!(a, b, 0i128);
531
532        if q > (usize::MAX as i128) {
533            usize::MAX
534        } else {
535            q as usize
536        }
537    }
538
539    /// - Integer part of `rhs` is multiplied **exactly** (pure i128 arithmetic).
540    /// - Fractional part (|frac| < 1) uses the 10¹⁵ scaling.
541    pub const fn mul_by_f(&self, rhs: Real) -> Self {
542        if rhs.is_nan() {
543            return Self::ZERO;
544        }
545        if rhs.is_infinite() {
546            if self.is_zero() {
547                return Self::ZERO;
548            }
549            let self_pos = self.sec > 0 || (self.sec == 0 && self.attos != 0);
550            return if (rhs > 0.0) == self_pos {
551                Self::MAX
552            } else {
553                Self::MIN
554            };
555        }
556        if self.is_zero() || rhs == 0.0 {
557            return Self::ZERO;
558        }
559
560        let self_attos = self.to_attos();
561        let max_attos = Self::MAX.to_attos();
562        let min_attos = Self::MIN.to_attos();
563
564        // Safe extraction of integer part (handles huge |rhs| without UB)
565        let int_part = if rhs >= (i128::MAX as Real) {
566            i128::MAX
567        } else if rhs <= (i128::MIN as Real) {
568            i128::MIN
569        } else {
570            floor_f(rhs) as i128
571        };
572
573        // Huge |rhs| → definitely saturates the type
574        if int_part == i128::MAX || int_part == i128::MIN {
575            let self_pos = self.sec > 0 || (self.sec == 0 && self.attos != 0);
576            return if (rhs > 0.0) == self_pos {
577                Self::MAX
578            } else {
579                Self::MIN
580            };
581        }
582
583        let frac_part = rhs - f!(int_part); // always in [0, 1)
584
585        // --- Integer part with explicit type-range saturation ---
586        let int_attos = if int_part == 0 {
587            0
588        } else if int_part > 0 {
589            if self_attos > 0 {
590                if int_part > max_attos / self_attos {
591                    max_attos
592                } else {
593                    self_attos * int_part
594                }
595            } else {
596                let abs_self = self_attos.wrapping_neg();
597                let abs_min = min_attos.wrapping_neg();
598                if int_part > abs_min / abs_self {
599                    min_attos
600                } else {
601                    self_attos * int_part
602                }
603            }
604        } else {
605            // int_part < 0
606            if self_attos > 0 {
607                let abs_int = int_part.wrapping_neg();
608                let abs_min = min_attos.wrapping_neg();
609                if abs_int > abs_min / self_attos {
610                    min_attos
611                } else {
612                    self_attos * int_part
613                }
614            } else {
615                let abs_self = self_attos.wrapping_neg();
616                let abs_int = int_part.wrapping_neg();
617                if abs_int > max_attos / abs_self {
618                    max_attos
619                } else {
620                    self_attos * int_part
621                }
622            }
623        };
624
625        // --- Fractional part: decomposed exact computation (never overflows i128) ---
626        const SCALE: i128 = 1_000_000_000_000_000; // 10¹⁵
627        let frac_scaled = (frac_part * (SCALE as Real)) as i128;
628
629        let frac_attos = if self_attos >= 0 {
630            let high = self_attos / SCALE;
631            let low = self_attos % SCALE;
632            let high_part = high * frac_scaled;
633            let low_part = (low * frac_scaled) / SCALE;
634            high_part + low_part
635        } else {
636            let abs_self = self_attos.wrapping_neg();
637            let high = abs_self / SCALE;
638            let low = abs_self % SCALE;
639            let high_part = high * frac_scaled;
640            let low_part = (low * frac_scaled) / SCALE;
641            let pos = high_part + low_part;
642            pos.wrapping_neg()
643        };
644
645        // Combine + final clamp (manual version because clamp is not const yet)
646        let total_attos = int_attos.saturating_add(frac_attos);
647        let clamped = if total_attos > max_attos {
648            max_attos
649        } else if total_attos < min_attos {
650            min_attos
651        } else {
652            total_attos
653        };
654
655        Self::from_attos(clamped, Scale::TAI)
656    }
657
658    /// Divides by a real number (routes through the high-precision `mul_by_f`).
659    #[inline]
660    pub const fn div_by_f(&self, rhs: Real) -> Self {
661        if rhs == 0.0 || rhs.is_nan() {
662            return if self.sec >= 0 { Self::MAX } else { Self::MIN };
663        }
664        self.mul_by_f(1.0 / rhs)
665    }
666
667    /// Divides this Dt by 2 (convenience wrapper).
668    #[inline]
669    pub const fn div_by_2(&self) -> Self {
670        self.div_by_f(2.0)
671    }
672
673    /// Internal helper used by add_1ms / add_1us / add_1ns.
674    #[doc(hidden)]
675    pub(crate) const fn add_attos_to(sec: &mut i64, attos: &mut u64, amount: u64) {
676        let total = *attos + amount;
677        let carry_sec = total / ATTOS_PER_SEC;
678        *attos = total % ATTOS_PER_SEC;
679        *sec = sec.saturating_add(carry_sec as i64);
680    }
681
682    /// Internal method to add or subtract a subsecond span in a given unit.
683    ///
684    /// This is the core implementation for all subsecond addition and subtraction
685    /// operations. It properly handles carry and borrow between the fractional
686    /// part (`attos`) and the whole seconds (`sec`), using saturating arithmetic
687    /// throughout.
688    #[doc(hidden)]
689    pub(crate) const fn add_attos_span(sec: &mut i64, attos: &mut u64, n: i64, unit: u64) {
690        if n == 0 {
691            return;
692        }
693
694        let mps = ATTOS_PER_SEC;
695
696        if n >= 0 {
697            let amount = (n as u64).saturating_mul(unit);
698            let total = attos.saturating_add(amount);
699
700            let carry = total / mps;
701            let new_frac = total % mps;
702
703            *sec = sec.saturating_add(carry as i64);
704            *attos = new_frac;
705        } else {
706            let amount = n.unsigned_abs().saturating_mul(unit);
707            let borrow_sec = amount / mps;
708            let borrow_frac = amount % mps;
709
710            *sec = sec.saturating_sub(borrow_sec as i64);
711
712            if *attos >= borrow_frac {
713                *attos -= borrow_frac;
714            } else {
715                *attos += mps - borrow_frac;
716                *sec = sec.saturating_sub(1);
717            }
718        }
719
720        // Final saturation clamp
721        if *sec == i64::MAX {
722            *attos = mps - 1;
723        } else if *sec == i64::MIN {
724            *attos = 0;
725        }
726    }
727
728    /// Returns the total time in seconds.
729    #[inline]
730    pub const fn to_sec(&mut self) -> i64 {
731        let Dt { sec, .. } = self.carry_over();
732        sec
733    }
734
735    pub(crate) const fn diff_raw_internal(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> Self {
736        if sub_a >= sub_b {
737            Self {
738                sec: sec_a.saturating_sub(sec_b),
739                attos: sub_a - sub_b,
740            }
741        } else {
742            Self {
743                sec: sec_a.saturating_sub(sec_b).saturating_sub(1),
744                attos: sub_a.saturating_add(ATTOS_PER_SEC.saturating_sub(sub_b)),
745            }
746        }
747    }
748
749    /// Clamps an `i128` to the representable range of `i64`.
750    pub(crate) const fn clamp_i128_to_i64(x: i128) -> i64 {
751        if x > i64::MAX as i128 {
752            i64::MAX
753        } else if x < i64::MIN as i128 {
754            i64::MIN
755        } else {
756            x as i64
757        }
758    }
759}