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, Spacetime, Real, Scale, 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    ///
29    /// The conversion is lossy by design, as [`Real`] provides approximately 15.95 decimal digits of precision.
30    /// For full exactness, use the integer components `sec` and `attos` directly or higher-precision arithmetic when available.
31    #[inline]
32    pub const fn to_sec_f(&self) -> Real {
33        f!(self.sec) + f!(self.attos) / ATTOS_PER_SECF
34    }
35
36    /// Advances this `Dt` by the given elapsed duration while applying the relativistic proper-time correction
37    /// derived from the supplied `Spacetime` model.
38    ///
39    /// This method is intended for simulation of remote clocks (e.g., Earth time as observed from a spacecraft).
40    /// For the spacecraft's own hardware proper-time clock, use the plain `add` method instead.
41    #[inline]
42    pub const fn adjusted_advance(&mut self, elapsed: &Dt, spacetime: &Spacetime) {
43        let dtau =
44            elapsed.add(Drift::from_spacetime(spacetime).time_diff_after(elapsed));
45        *self = self.add(dtau);
46    }
47
48    /// Advances this `Dt` by the given elapsed duration while applying the relativistic proper-time correction
49    /// from a pre-computed `Drift` value.
50    ///
51    /// This is an optimized variant of `adjusted_advance` for callers that already hold a `Drift` instance.
52    /// It is intended for simulation of remote clocks; the spacecraft's own hardware clock should use the plain `add` method.
53    #[inline]
54    pub const fn adjusted_advance_using_drift(&mut self, elapsed: &Dt, drift: &Drift) {
55        let dtau = elapsed.add(drift.time_diff_after(elapsed));
56        *self = self.add(dtau);
57    }
58
59    /// Computes the signed duration between this `Dt` and another `Dt`.
60    #[inline]
61    pub const fn to_diff_raw(&self, other: Self) -> Dt {
62        Self::diff_raw_internal(self.sec, self.attos, other.sec, other.attos)
63    }
64
65    /// Computes the signed duration between this `Dt` and another `Dt` as a float.
66    #[inline]
67    pub const fn to_diff_raw_f(&self, other: Self) -> Real {
68        self.to_sec_f() - other.to_sec_f()
69    }
70
71    /// Adds exactly 1 second to this time value using saturating arithmetic.
72    #[inline]
73    pub const fn add_1sec(&mut self) {
74        self.sec = self.sec.saturating_add(1);
75    }
76
77    /// Adds exactly 1 minute (60 seconds) to this time value using saturating arithmetic.
78    #[inline]
79    pub const fn add_1min(&mut self) {
80        self.sec = self.sec.saturating_add(60);
81    }
82
83    /// Adds exactly 1 hour (3600 seconds) to this time value using saturating arithmetic.
84    #[inline]
85    pub const fn add_1hr(&mut self) {
86        self.sec = self.sec.saturating_add(3600);
87    }
88
89    /// Adds exactly 1 millisecond to this time value.
90    ///
91    /// This affects the subsecond component and may cause a carry into the seconds field.
92    #[inline]
93    pub const fn add_1ms(&mut self) {
94        Self::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_MS);
95    }
96
97    /// Adds exactly 1 microsecond 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_1us(&mut self) {
102        Self::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_US);
103    }
104
105    /// Adds exactly 1 nanosecond 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_1ns(&mut self) {
110        Self::add_attos_to(&mut self.sec, &mut self.attos, ATTOS_PER_NS);
111    }
112
113    /// Adds the specified number of seconds to this time value using saturating arithmetic.
114    #[inline]
115    pub const fn add_sec(&mut self, n: i64) {
116        self.sec = self.sec.saturating_add(n);
117    }
118
119    /// Adds the specified number of minutes to this time value using saturating arithmetic.
120    #[inline]
121    pub const fn add_min(&mut self, n: i64) {
122        self.sec = self.sec.saturating_add(n.saturating_mul(60));
123    }
124
125    /// Adds the specified number of hours to this time value using saturating arithmetic.
126    #[inline]
127    pub const fn add_hr(&mut self, n: i64) {
128        self.sec = self.sec.saturating_add(n.saturating_mul(3600));
129    }
130
131    /// Adds the specified number of milliseconds to this time value.
132    ///
133    /// Handles carry into the seconds field using saturating logic.
134    #[inline]
135    pub const fn add_ms(&mut self, n: i64) {
136        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_MS);
137    }
138
139    /// Adds the specified number of microseconds to this time value.
140    ///
141    /// Handles carry into the seconds field using saturating logic.
142    #[inline]
143    pub const fn add_us(&mut self, n: i64) {
144        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_US);
145    }
146
147    /// Adds the specified number of nanoseconds to this time value.
148    ///
149    /// Handles carry into the seconds field using saturating logic.
150    #[inline]
151    pub const fn add_ns(&mut self, n: i64) {
152        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_NS);
153    }
154
155    /// Adds the specified number of picoseconds to this time value.
156    ///
157    /// Handles carry into the seconds field using saturating logic.
158    #[inline]
159    pub const fn add_ps(&mut self, n: i64) {
160        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_PS);
161    }
162
163    /// Adds the specified number of femtoseconds to this time value.
164    ///
165    /// Handles carry into the seconds field using saturating logic.
166    #[inline]
167    pub const fn add_fs(&mut self, n: i64) {
168        Self::add_attos_span(&mut self.sec, &mut self.attos, n, ATTOS_PER_FS);
169    }
170
171    /// Adds the specified number of attoseconds to this time value.
172    ///
173    /// Handles carry into the seconds field using saturating logic.
174    #[inline]
175    pub const fn add_attos(&mut self, n: i64) {
176        Self::add_attos_span(&mut self.sec, &mut self.attos, n, 1);
177    }
178
179    /// Subtracts exactly 1 hour (3600 seconds) from this time value using saturating arithmetic.
180    #[inline]
181    pub const fn sub_1hr(&mut self) {
182        self.sec = self.sec.saturating_sub(3600);
183    }
184
185    /// Subtracts exactly 1 minute (60 seconds) from this time value using saturating arithmetic.
186    #[inline]
187    pub const fn sub_1min(&mut self) {
188        self.sec = self.sec.saturating_sub(60);
189    }
190
191    /// Subtracts exactly 1 second from this time value using saturating arithmetic.
192    #[inline]
193    pub const fn sub_1sec(&mut self) {
194        self.sec = self.sec.saturating_sub(1);
195    }
196
197    /// Subtracts exactly 1 millisecond from this time value.
198    ///
199    /// This affects the subsecond component and may cause a borrow from the seconds field.
200    #[inline]
201    pub const fn sub_1ms(&mut self) {
202        Self::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_MS);
203    }
204
205    /// Subtracts exactly 1 microsecond 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_1us(&mut self) {
210        Self::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_US);
211    }
212
213    /// Subtracts exactly 1 nanosecond 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_1ns(&mut self) {
218        Self::add_attos_span(&mut self.sec, &mut self.attos, -1, ATTOS_PER_NS);
219    }
220
221    /// Subtracts the specified number of seconds from this time value using saturating arithmetic.
222    #[inline]
223    pub const fn sub_sec(&mut self, n: i64) {
224        self.sec = self.sec.saturating_sub(n);
225    }
226
227    /// Subtracts the specified number of minutes from this time value using saturating arithmetic.
228    #[inline]
229    pub const fn sub_min(&mut self, n: i64) {
230        self.sec = self.sec.saturating_sub(n.saturating_mul(60));
231    }
232
233    /// Subtracts the specified number of hours from this time value using saturating arithmetic.
234    #[inline]
235    pub const fn sub_hr(&mut self, n: i64) {
236        self.sec = self.sec.saturating_sub(n.saturating_mul(3600));
237    }
238
239    /// Subtracts the specified number of milliseconds from this time value.
240    ///
241    /// Handles borrow from the seconds field using saturating logic.
242    #[inline]
243    pub const fn sub_ms(&mut self, n: i64) {
244        Self::add_attos_span(
245            &mut self.sec,
246            &mut self.attos,
247            n.saturating_neg(),
248            ATTOS_PER_MS,
249        );
250    }
251
252    /// Subtracts the specified number of microseconds from this time value.
253    ///
254    /// Handles borrow from the seconds field using saturating logic.
255    #[inline]
256    pub const fn sub_us(&mut self, n: i64) {
257        Self::add_attos_span(
258            &mut self.sec,
259            &mut self.attos,
260            n.saturating_neg(),
261            ATTOS_PER_US,
262        );
263    }
264
265    /// Subtracts the specified number of nanoseconds from this time value.
266    ///
267    /// Handles borrow from the seconds field using saturating logic.
268    #[inline]
269    pub const fn sub_ns(&mut self, n: i64) {
270        Self::add_attos_span(
271            &mut self.sec,
272            &mut self.attos,
273            n.saturating_neg(),
274            ATTOS_PER_NS,
275        );
276    }
277
278    /// Subtracts the specified number of picoseconds from this time value.
279    ///
280    /// Handles borrow from the seconds field using saturating logic.
281    #[inline]
282    pub const fn sub_ps(&mut self, n: i64) {
283        Self::add_attos_span(
284            &mut self.sec,
285            &mut self.attos,
286            n.saturating_neg(),
287            ATTOS_PER_PS,
288        );
289    }
290
291    /// Subtracts the specified number of femtoseconds from this time value.
292    ///
293    /// Handles borrow from the seconds field using saturating logic.
294    #[inline]
295    pub const fn sub_fs(&mut self, n: i64) {
296        Self::add_attos_span(
297            &mut self.sec,
298            &mut self.attos,
299            n.saturating_neg(),
300            ATTOS_PER_FS,
301        );
302    }
303
304    /// Subtracts the specified number of attoseconds from this time value.
305    ///
306    /// Handles borrow from the seconds field using saturating logic.
307    #[inline]
308    pub const fn sub_attos(&mut self, n: i64) {
309        Self::add_attos_span(&mut self.sec, &mut self.attos, n.saturating_neg(), 1);
310    }
311
312    /// Total attoseconds (exact i128 representation within the representable range).
313    #[inline]
314    pub const fn to_attos(&self) -> i128 {
315        (self.sec as i128) * ATTOS_PER_SEC_I128 + (self.attos as i128)
316    }
317
318    /// Returns the total time in milliseconds.
319    #[inline]
320    pub const fn to_ms(&self) -> i128 {
321        self.to_attos() / (ATTOS_PER_MS as i128)
322    }
323
324    /// Returns the total time in microseconds.
325    #[inline]
326    pub const fn to_us(&self) -> i128 {
327        self.to_attos() / (ATTOS_PER_US as i128)
328    }
329
330    /// Returns the total time in nanoseconds.
331    #[inline]
332    pub const fn to_ns(&self) -> i128 {
333        self.to_attos() / (ATTOS_PER_NS as i128)
334    }
335
336    /// Returns the total time in picoseconds.
337    #[inline]
338    pub const fn to_ps(&self) -> i128 {
339        self.to_attos() / (ATTOS_PER_PS as i128)
340    }
341
342    /// Returns the total time in femtoseconds.
343    #[inline]
344    pub const fn to_fs(&self) -> i128 {
345        self.to_attos() / (ATTOS_PER_FS as i128)
346    }
347
348    /// Core saturating add for (sec, attos) pairs.
349    pub(crate) const fn add_time(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> (i64, u64) {
350        let mut sec = sec_a.saturating_add(sec_b);
351        let mut attos = sub_a as i64 + sub_b as i64;
352
353        if attos >= ATTOS_PER_SEC as i64 {
354            if sec < i64::MAX {
355                sec = sec.saturating_add(1);
356            }
357            attos -= ATTOS_PER_SEC as i64;
358        } else if attos < 0 {
359            if sec > i64::MIN {
360                sec = sec.saturating_sub(1);
361            }
362            attos += ATTOS_PER_SEC as i64;
363        }
364
365        let attos = if sec == i64::MAX {
366            ATTOS_PER_SEC - 1
367        } else if sec == i64::MIN {
368            0
369        } else {
370            attos as u64
371        };
372
373        (sec, attos)
374    }
375
376    /// Core saturating sub for (sec, attos) pairs.
377    pub(crate) const fn sub_time(sec_a: i64, sub_a: u64, sec_b: i64, sub_b: u64) -> (i64, u64) {
378        let mut sec = sec_a.saturating_sub(sec_b);
379        let mut attos = sub_a as i64 - sub_b as i64;
380
381        if attos < 0 {
382            if sec > i64::MIN {
383                sec = sec.saturating_sub(1);
384            }
385            attos += ATTOS_PER_SEC as i64;
386        } else if attos >= ATTOS_PER_SEC as i64 {
387            if sec < i64::MAX {
388                sec = sec.saturating_add(1);
389            }
390            attos -= ATTOS_PER_SEC as i64;
391        }
392
393        let attos = if sec == i64::MAX {
394            ATTOS_PER_SEC - 1
395        } else if sec == i64::MIN {
396            0
397        } else {
398            attos as u64
399        };
400
401        (sec, attos)
402    }
403
404    /// Returns `true` if this time is exactly zero.
405    #[inline]
406    pub const fn is_zero(&self) -> bool {
407        self.sec == 0 && self.attos == 0
408    }
409
410    /// Returns `true` if this time is strictly positive **(> 0)**.
411    #[inline]
412    pub const fn is_positive(&self) -> bool {
413        if self.sec > 0 {
414            true
415        } else if self.sec == 0 {
416            self.attos != 0
417        } else {
418            let k = (-self.sec) as u64;
419            let quot = self.attos / ATTOS_PER_SEC;
420            let rem = self.attos % ATTOS_PER_SEC;
421
422            quot > k || (quot == k && rem > 0)
423        }
424    }
425
426    /// Multiplies this time by an integer scalar (exact).
427    ///
428    /// Uses 128-bit arithmetic internally.
429    pub const fn mul(self, rhs: i64) -> Self {
430        if rhs == 0 || self.is_zero() {
431            return Self::ZERO;
432        }
433        let total: i128 = self.to_attos().saturating_mul(rhs as i128);
434        Self::from_attos(total, Scale::TAI)
435    }
436
437    /// Divides this time by an integer scalar (exact floor division).
438    ///
439    /// Returns `ZERO` if `rhs == 0`.
440    /// Uses floor division (toward negative infinity) for consistency
441    /// with the existing `floor` method.
442    pub const fn div(self, rhs: i64) -> Self {
443        if rhs == 0 || self.is_zero() {
444            return Self::ZERO;
445        }
446        let total = self.to_attos();
447        let result = total.div_euclid(rhs as i128);
448        Self::from_attos(result, Scale::TAI)
449    }
450
451    /// Returns the **largest** multiple of `unit` that is ≤ `self`.
452    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
453    pub const fn floor(&self, unit: Self) -> Self {
454        if unit.is_zero() {
455            return *self;
456        }
457        let a = self.to_attos();
458        let b = unit.to_attos();
459        let q = a.div_euclid(b);
460        let result = q.wrapping_mul(b);
461        Self::from_attos(result, Scale::TAI)
462    }
463
464    /// Returns the **smallest** multiple of `unit` that is ≥ `self`.
465    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
466    pub const fn ceil(&self, unit: Self) -> Self {
467        if unit.is_zero() {
468            return *self;
469        }
470        let a = self.to_attos();
471        let b = unit.to_attos();
472        // ceil(a/b) ≡ −floor(−a/b)
473        let neg_a = a.wrapping_neg();
474        let q = neg_a.div_euclid(b);
475        let q_ceil = q.wrapping_neg();
476        let result = q_ceil.wrapping_mul(b);
477        Self::from_attos(result, Scale::TAI)
478    }
479
480    /// Returns the nearest multiple of `unit`.
481    /// Halfway cases round **away from zero** (matches old `f64::round`).
482    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
483    pub const fn round(&self, unit: Self) -> Self {
484        if unit.is_zero() {
485            return *self;
486        }
487        let a = self.to_attos();
488        let b = unit.to_attos();
489
490        let q = a.div_euclid(b);
491        let r = a.rem_euclid(b);
492
493        // half = |b| / 2  (rounded up for tie-breaking away from zero)
494        let abs_b = b.wrapping_abs();
495        let two = 2i128;
496        let half = (abs_b + 1) / two;
497
498        if r >= half {
499            // round away from zero
500            let one = 1i128;
501            let q_rounded = if a < 0 { q - one } else { q + one };
502            let result = q_rounded.wrapping_mul(b);
503            Self::from_attos(result, Scale::TAI)
504        } else {
505            let result = q.wrapping_mul(b);
506            Self::from_attos(result, Scale::TAI)
507        }
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 = a.div_euclid(b);
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        self.carry_over();
721        self.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}