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