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(always)]
420    pub const fn is_positive(&self) -> bool {
421        self.to_attos() > 0
422    }
423
424    /// Multiplies this time by an integer scalar (exact).
425    ///
426    /// Uses 128-bit arithmetic internally.
427    pub const fn mul(self, rhs: i64) -> Self {
428        if rhs == 0 || self.is_zero() {
429            return Self::ZERO;
430        }
431        let total: i128 = self.to_attos().saturating_mul(rhs as i128);
432        Self::from_attos(total, Scale::TAI)
433    }
434
435    /// Divides this `Dt` by an integer scalar.
436    ///
437    /// Uses truncating division (rounds toward zero), same as normal integer division.
438    /// Returns `ZERO` if `rhs == 0`.
439    pub const fn div(self, rhs: i64) -> Self {
440        if rhs == 0 || self.is_zero() {
441            return Self::ZERO;
442        }
443        let total = self.to_attos();
444        let result = total / (rhs as i128);
445        Self::from_attos(result, Scale::TAI)
446    }
447
448    /// Returns the **largest** multiple of `unit` that is ≤ `self`.
449    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
450    pub const fn floor(&self, unit: Self) -> Self {
451        if unit.is_zero() {
452            return *self;
453        }
454        let a = self.to_attos();
455        let b = unit.to_attos();
456        let q = safe_div_euc!(a, b, 0i128);
457        let result = q.wrapping_mul(b);
458        Self::from_attos(result, Scale::TAI)
459    }
460
461    /// Returns the **smallest** multiple of `unit` that is ≥ `self`.
462    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
463    pub const fn ceil(&self, unit: Self) -> Self {
464        if unit.is_zero() {
465            return *self;
466        }
467        let a = self.to_attos();
468        let b = unit.to_attos();
469        // ceil(a/b) ≡ −floor(−a/b)
470        let neg_a = a.wrapping_neg();
471        let q = safe_div_euc!(neg_a, b, 0i128);
472        let q_ceil = q.wrapping_neg();
473        let result = q_ceil.wrapping_mul(b);
474        Self::from_attos(result, Scale::TAI)
475    }
476
477    /// Returns the nearest multiple of `unit`.
478    ///
479    /// Halfway cases round **away from zero** (e.g. `2.5 → 3.0`, `-2.5 → -3.0`),
480    /// matching the behavior of the old `f64::round()`.
481    ///
482    /// - If `unit` is zero, returns `self` unchanged (preserves full precision).
483    /// - Uses Euclidean division internally for correct behavior on negative values.
484    /// - The result is always a multiple of `unit`.
485    pub const fn round(&self, unit: Self) -> Self {
486        if unit.is_zero() {
487            return *self;
488        }
489
490        let a = self.to_attos();
491        let b = unit.to_attos();
492
493        let abs_a = a.wrapping_abs();
494        let abs_b = b.wrapping_abs();
495
496        let q = safe_div_euc!(abs_a, abs_b, 0i128);
497        let r = safe_rem_euc!(abs_a, abs_b, 0i128);
498
499        let half = (abs_b + 1) / 2;
500
501        let q_rounded = if r >= half { q + 1 } else { q };
502
503        let rounded_abs = q_rounded.wrapping_mul(abs_b);
504
505        let result = if a < 0 { -rounded_abs } else { rounded_abs };
506
507        Self::from_attos(result, Scale::TAI)
508    }
509
510    /// Returns `floor(|self| / |unit|)` as `usize`, saturating at `usize::MAX`.
511    ///
512    /// Fully exact integer arithmetic using 128-bit intermediaries. Used by `TimeRange::len`.
513    pub const fn abs_div_floor(&self, unit: Self) -> usize {
514        if unit.is_zero() {
515            return 0;
516        }
517        let a = self.to_attos().wrapping_abs();
518        let b = unit.to_attos().wrapping_abs();
519        let q = safe_div_euc!(a, b, 0i128);
520
521        if q > (usize::MAX as i128) {
522            usize::MAX
523        } else {
524            q as usize
525        }
526    }
527
528    /// - Integer part of `rhs` is multiplied **exactly** (pure i128 arithmetic).
529    /// - Fractional part (|frac| < 1) uses the 10¹⁵ scaling.
530    pub const fn mul_by_f(&self, rhs: Real) -> Self {
531        if rhs.is_nan() {
532            return Self::ZERO;
533        }
534        if rhs.is_infinite() {
535            if self.is_zero() {
536                return Self::ZERO;
537            }
538            let self_pos = self.sec > 0 || (self.sec == 0 && self.attos != 0);
539            return if (rhs > 0.0) == self_pos {
540                Self::MAX
541            } else {
542                Self::MIN
543            };
544        }
545        if self.is_zero() || rhs == 0.0 {
546            return Self::ZERO;
547        }
548
549        let self_attos = self.to_attos();
550        let max_attos = Self::MAX.to_attos();
551        let min_attos = Self::MIN.to_attos();
552
553        // Safe extraction of integer part (handles huge |rhs| without UB)
554        let int_part = if rhs >= (i128::MAX as Real) {
555            i128::MAX
556        } else if rhs <= (i128::MIN as Real) {
557            i128::MIN
558        } else {
559            floor_f(rhs) as i128
560        };
561
562        // Huge |rhs| → definitely saturates the type
563        if int_part == i128::MAX || int_part == i128::MIN {
564            let self_pos = self.sec > 0 || (self.sec == 0 && self.attos != 0);
565            return if (rhs > 0.0) == self_pos {
566                Self::MAX
567            } else {
568                Self::MIN
569            };
570        }
571
572        let frac_part = rhs - f!(int_part); // always in [0, 1)
573
574        // --- Integer part with explicit type-range saturation ---
575        let int_attos = if int_part == 0 {
576            0
577        } else if int_part > 0 {
578            if self_attos > 0 {
579                if int_part > max_attos / self_attos {
580                    max_attos
581                } else {
582                    self_attos * int_part
583                }
584            } else {
585                let abs_self = self_attos.wrapping_neg();
586                let abs_min = min_attos.wrapping_neg();
587                if int_part > abs_min / abs_self {
588                    min_attos
589                } else {
590                    self_attos * int_part
591                }
592            }
593        } else {
594            // int_part < 0
595            if self_attos > 0 {
596                let abs_int = int_part.wrapping_neg();
597                let abs_min = min_attos.wrapping_neg();
598                if abs_int > abs_min / self_attos {
599                    min_attos
600                } else {
601                    self_attos * int_part
602                }
603            } else {
604                let abs_self = self_attos.wrapping_neg();
605                let abs_int = int_part.wrapping_neg();
606                if abs_int > max_attos / abs_self {
607                    max_attos
608                } else {
609                    self_attos * int_part
610                }
611            }
612        };
613
614        // --- Fractional part: decomposed exact computation (never overflows i128) ---
615        const SCALE: i128 = 1_000_000_000_000_000; // 10¹⁵
616        let frac_scaled = (frac_part * (SCALE as Real)) as i128;
617
618        let frac_attos = if self_attos >= 0 {
619            let high = self_attos / SCALE;
620            let low = self_attos % SCALE;
621            let high_part = high * frac_scaled;
622            let low_part = (low * frac_scaled) / SCALE;
623            high_part + low_part
624        } else {
625            let abs_self = self_attos.wrapping_neg();
626            let high = abs_self / SCALE;
627            let low = abs_self % SCALE;
628            let high_part = high * frac_scaled;
629            let low_part = (low * frac_scaled) / SCALE;
630            let pos = high_part + low_part;
631            pos.wrapping_neg()
632        };
633
634        // Combine + final clamp (manual version because clamp is not const yet)
635        let total_attos = int_attos.saturating_add(frac_attos);
636        let clamped = if total_attos > max_attos {
637            max_attos
638        } else if total_attos < min_attos {
639            min_attos
640        } else {
641            total_attos
642        };
643
644        Self::from_attos(clamped, Scale::TAI)
645    }
646
647    /// Divides by a real number (routes through the high-precision `mul_by_f`).
648    #[inline]
649    pub const fn div_by_f(&self, rhs: Real) -> Self {
650        if rhs == 0.0 || rhs.is_nan() {
651            return if self.sec >= 0 { Self::MAX } else { Self::MIN };
652        }
653        self.mul_by_f(1.0 / rhs)
654    }
655
656    /// Divides this Dt by 2 (convenience wrapper).
657    #[inline]
658    pub const fn div_by_2(&self) -> Self {
659        self.div_by_f(2.0)
660    }
661
662    /// Internal helper used by add_1ms / add_1us / add_1ns.
663    #[doc(hidden)]
664    pub(crate) const fn add_attos_to(sec: &mut i64, attos: &mut u64, amount: u64) {
665        let total = *attos + amount;
666        let carry_sec = total / ATTOS_PER_SEC;
667        *attos = total % ATTOS_PER_SEC;
668        *sec = sec.saturating_add(carry_sec as i64);
669    }
670
671    /// Internal method to add or subtract a subsecond span in a given unit.
672    ///
673    /// This is the core implementation for all subsecond addition and subtraction
674    /// operations. It properly handles carry and borrow between the fractional
675    /// part (`attos`) and the whole seconds (`sec`), using saturating arithmetic
676    /// throughout.
677    #[doc(hidden)]
678    pub(crate) const fn add_attos_span(sec: &mut i64, attos: &mut u64, n: i64, unit: u64) {
679        if n == 0 {
680            return;
681        }
682
683        let mps = ATTOS_PER_SEC;
684
685        if n >= 0 {
686            let amount = (n as u64).saturating_mul(unit);
687            let total = attos.saturating_add(amount);
688
689            let carry = total / mps;
690            let new_frac = total % mps;
691
692            *sec = sec.saturating_add(carry as i64);
693            *attos = new_frac;
694        } else {
695            let amount = n.unsigned_abs().saturating_mul(unit);
696            let borrow_sec = amount / mps;
697            let borrow_frac = amount % mps;
698
699            *sec = sec.saturating_sub(borrow_sec as i64);
700
701            if *attos >= borrow_frac {
702                *attos -= borrow_frac;
703            } else {
704                *attos += mps - borrow_frac;
705                *sec = sec.saturating_sub(1);
706            }
707        }
708
709        // Final saturation clamp
710        if *sec == i64::MAX {
711            *attos = mps - 1;
712        } else if *sec == i64::MIN {
713            *attos = 0;
714        }
715    }
716
717    /// Returns the total time in seconds.
718    #[inline]
719    pub const fn to_sec(&mut self) -> i64 {
720        let Dt { sec, .. } = self.carry_over();
721        sec
722    }
723
724    pub(crate) const fn diff_raw_internal(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> Self {
725        if sub_a >= sub_b {
726            Self {
727                sec: sec_a.saturating_sub(sec_b),
728                attos: sub_a - sub_b,
729            }
730        } else {
731            Self {
732                sec: sec_a.saturating_sub(sec_b).saturating_sub(1),
733                attos: sub_a.saturating_add(ATTOS_PER_SEC.saturating_sub(sub_b)),
734            }
735        }
736    }
737
738    /// Clamps an `i128` to the representable range of `i64`.
739    #[inline(always)]
740    pub(crate) const fn clamp_i128_to_i64(x: i128) -> i64 {
741        let y = x as i64;
742        if x == y as i128 {
743            y
744        } else if x > 0 {
745            i64::MAX
746        } else {
747            i64::MIN
748        }
749    }
750}