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