deep_time/dt/arithmetic.rs
1use crate::{
2 ATTOS_PER_FS_I128, ATTOS_PER_MS_I128, ATTOS_PER_NS_I128, ATTOS_PER_PS_I128, ATTOS_PER_SEC_I128,
3 ATTOS_PER_SECF, ATTOS_PER_US_I128, Drift, Dt, Real, Scale, Spacetime, floor_f,
4};
5
6impl Dt {
7 /// Saturating add, keeps `self`'s `scale` and `target`.
8 #[inline]
9 pub const fn add(&self, dt: Dt) -> Dt {
10 if !dt.is_zero() {
11 Dt::new(self.attos.saturating_add(dt.attos), self.scale, self.target)
12 } else {
13 *self
14 }
15 }
16
17 /// Saturating sub, keeps `self`'s `scale` and `target`.
18 #[inline]
19 pub const fn sub(&self, dt: Dt) -> Dt {
20 if !dt.is_zero() {
21 Dt::new(self.attos.saturating_sub(dt.attos), self.scale, self.target)
22 } else {
23 *self
24 }
25 }
26
27 /// If this time were turned into [`i128`] seconds and [`u64`] (always
28 /// pushing to the positive) fractional attoseconds, this returns the
29 /// whole seconds part.
30 ///
31 /// To just get seconds rounded to the nearest second use
32 /// [`Dt::to_sec_rounded`](../struct.Dt.html#method.to_sec_rounded)
33 /// instead.
34 ///
35 /// ## Examples
36 ///
37 /// ```rust
38 /// use deep_time::{Dt, Scale};
39 ///
40 /// // negative 1.3 seconds
41 /// let dt = Dt::span(-1_300_000_000_000_000_000);
42 ///
43 /// // becomes positive 700ms
44 /// let frac = dt.to_sec_ufrac();
45 /// assert_eq!(frac, 700_000_000_000_000_000);
46 ///
47 /// // becomes negative 2 seconds
48 /// let sec = dt.to_sec();
49 /// assert_eq!(sec, -2);
50 ///
51 /// let dt = Dt::span(1_300_000_000_000_000_000);
52 ///
53 /// assert_eq!(dt.to_sec(), 1);
54 /// assert_eq!(dt.to_sec_ufrac(), 300_000_000_000_000_000);
55 ///
56 /// // if you just want rounded seconds
57 /// // use to_sec_rounded() instead
58 /// let dt = Dt::span(-1_300_000_000_000_000_000);
59 /// let sec = dt.to_sec_rounded();
60 /// assert_eq!(sec, -1);
61 /// ```
62 #[inline(always)]
63 pub const fn to_sec(&self) -> i128 {
64 self.attos.div_euclid(ATTOS_PER_SEC_I128)
65 }
66
67 #[inline(always)]
68 pub const fn to_sec_rounded(&self) -> i128 {
69 self.round_to_sec().to_sec()
70 }
71
72 #[inline(always)]
73 pub const fn to_sec64_rounded(&self) -> i64 {
74 Self::i128_to_i64(self.round_to_sec().to_sec())
75 }
76
77 /// If this time were turned into [`i64`] seconds and [`u64`] (always
78 /// pushing to the positive) fractional attoseconds, this returns the
79 /// whole seconds part.
80 ///
81 /// To just get seconds rounded to the nearest second use
82 /// [`Dt::to_sec_rounded`](../struct.Dt.html#method.to_sec_rounded)
83 /// instead.
84 ///
85 /// ## Examples
86 ///
87 /// ```rust
88 /// use deep_time::{Dt, Scale};
89 ///
90 /// // negative 1.3 seconds
91 /// let dt = Dt::span(-1_300_000_000_000_000_000);
92 ///
93 /// // becomes positive 700ms
94 /// let frac = dt.to_sec_ufrac();
95 /// assert_eq!(frac, 700_000_000_000_000_000);
96 ///
97 /// // becomes negative 2 seconds
98 /// let sec = dt.to_sec64();
99 /// assert_eq!(sec, -2);
100 ///
101 /// let dt = Dt::span(1_300_000_000_000_000_000);
102 ///
103 /// assert_eq!(dt.to_sec64(), 1);
104 /// assert_eq!(dt.to_sec_ufrac(), 300_000_000_000_000_000);
105 ///
106 /// // if you just want rounded seconds
107 /// // use to_sec_rounded() instead
108 /// let dt = Dt::span(-1_300_000_000_000_000_000);
109 /// let sec = dt.to_sec_rounded();
110 /// assert_eq!(sec, -1);
111 /// ```
112 #[inline(always)]
113 pub const fn to_sec64(&self) -> i64 {
114 Self::i128_to_i64(self.attos.div_euclid(ATTOS_PER_SEC_I128))
115 }
116
117 /// Converts this `Dt` to a floating-point number of seconds since the reference
118 /// epoch of its associated scale.
119 ///
120 /// - The conversion is lossy, as [`Real`] provides approximately 15.95 decimal
121 /// digits of precision.
122 pub const fn to_sec_f(&self) -> Real {
123 let attos = self.attos;
124
125 if attos == 0 {
126 return 0.0;
127 }
128 let sec = attos.div_euclid(ATTOS_PER_SEC_I128);
129 let rem = attos.rem_euclid(ATTOS_PER_SEC_I128); // always in [0, aps)
130
131 if sec < 0 && rem > ATTOS_PER_SEC_I128 / 2 {
132 // original cancellation-avoidance path
133 let small = ATTOS_PER_SEC_I128 - rem;
134 let small_f = f!(small as u64) / ATTOS_PER_SECF;
135 (sec as f64) + 1.0 - small_f
136 } else {
137 (sec as f64) + f!(rem as u64) / ATTOS_PER_SECF
138 }
139 }
140
141 /// If this time were turned into seconds, this returns the fractional attoseconds part.
142 #[inline(always)]
143 pub const fn to_sec_frac(&self) -> i64 {
144 (self.attos % ATTOS_PER_SEC_I128) as i64
145 }
146
147 /// If this time were turned into i64 seconds and u64 (always pushing to the positive)
148 /// fractional attoseconds, this returns the fractional attoseconds part.
149 ///
150 /// - Always returns a value in the range `0 ≤ x < ATTOS_PER_SEC`.
151 /// - For negative [`Dt`]s this is **not** simply the decimal part of the time in seconds.
152 ///
153 /// ## Examples
154 ///
155 /// ```rust
156 /// use deep_time::{Dt, Scale};
157 ///
158 /// // negative 1.3 seconds
159 /// let dt = Dt::span(-1_300_000_000_000_000_000);
160 ///
161 /// // becomes positive 700ms
162 /// let frac = dt.to_sec_ufrac();
163 /// assert_eq!(frac, 700_000_000_000_000_000);
164 ///
165 /// // becomes -2 seconds
166 /// let sec = dt.to_sec64();
167 /// assert_eq!(sec, -2);
168 ///
169 /// let dt = Dt::span(1_300_000_000_000_000_000);
170 ///
171 /// assert_eq!(dt.to_sec64(), 1);
172 /// assert_eq!(dt.to_sec_ufrac(), 300_000_000_000_000_000);
173 /// ```
174 #[inline(always)]
175 pub const fn to_sec_ufrac(&self) -> u64 {
176 self.attos.rem_euclid(ATTOS_PER_SEC_I128) as u64
177 }
178
179 /// Returns a new [`Dt`] rounded to the nearest second.
180 #[inline(always)]
181 pub const fn round_to_sec(&self) -> Dt {
182 self.round(Dt::span(ATTOS_PER_SEC_I128))
183 }
184
185 /// Computes the signed duration between this [`Dt`] and another [`Dt`].
186 #[inline]
187 pub const fn to_diff_raw(&self, other: Dt) -> Dt {
188 Dt::new(
189 self.attos.saturating_sub(other.attos),
190 self.scale,
191 self.target,
192 )
193 }
194
195 /// Computes the signed duration between this [`Dt`] and another [`Dt`] as a float.
196 #[inline]
197 pub const fn to_diff_raw_f(&self, other: Dt) -> Real {
198 self.to_sec_f() - other.to_sec_f()
199 }
200
201 /// Low level constructor from total attoseconds since a given epoch.
202 ///
203 /// Simply adds the total attoseconds to the epoch. Does not perform
204 /// any time scale conversions.
205 ///
206 /// ## Examples
207 ///
208 /// ```rust
209 /// use deep_time::{Dt, Scale};
210 ///
211 /// // A leap second from the middle of the table (36 leap seconds accumulated)
212 /// let original = Dt::from_ymd(2015, 6, 30, 23, 59, 60, 123_456_789_000_000_000, Scale::UTC);
213 ///
214 /// // Round-trip through canonical attoseconds
215 /// let canon = original.to_diff_raw(Dt::UNIX_EPOCH).to_attos();
216 /// let roundtrip1 = Dt::from_diff_raw(canon, Dt::UNIX_EPOCH);
217 ///
218 /// assert_eq!(original, roundtrip1, "Canonical round-trip failed");
219 /// ```
220 #[inline]
221 pub const fn from_diff_raw(attos: i128, epoch: Dt) -> Dt {
222 epoch.add(Dt::new(attos, epoch.scale, epoch.target))
223 }
224
225 /// Adds the specified number of attoseconds to this time value.
226 #[inline(always)]
227 pub const fn add_attos(&self, n: i128) -> Dt {
228 Dt::new(self.attos.saturating_add(n), self.scale, self.target)
229 }
230
231 /// Adds the specified number of seconds to this time value using saturating arithmetic.
232 #[inline(always)]
233 pub const fn add_sec(&self, n: i128) -> Dt {
234 self.add_attos(n.saturating_mul(ATTOS_PER_SEC_I128))
235 }
236
237 /// Adds the specified number of milliseconds to this time value.
238 #[inline(always)]
239 pub const fn add_ms(&self, n: i128) -> Dt {
240 self.add_attos(n.saturating_mul(ATTOS_PER_MS_I128))
241 }
242
243 /// Adds the specified number of microseconds to this time value.
244 #[inline(always)]
245 pub const fn add_us(&self, n: i128) -> Dt {
246 self.add_attos(n.saturating_mul(ATTOS_PER_US_I128))
247 }
248
249 /// Adds the specified number of nanoseconds to this time value.
250 #[inline(always)]
251 pub const fn add_ns(&self, n: i128) -> Dt {
252 self.add_attos(n.saturating_mul(ATTOS_PER_NS_I128))
253 }
254
255 /// Adds the specified number of picoseconds to this time value.
256 #[inline(always)]
257 pub const fn add_ps(&self, n: i128) -> Dt {
258 self.add_attos(n.saturating_mul(ATTOS_PER_PS_I128))
259 }
260
261 /// Adds the specified number of femtoseconds to this time value.
262 #[inline(always)]
263 pub const fn add_fs(&self, n: i128) -> Dt {
264 self.add_attos(n.saturating_mul(ATTOS_PER_FS_I128))
265 }
266
267 /// Adds the specified number of minutes to this time value using saturating arithmetic.
268 #[inline]
269 pub const fn add_min(&self, n: i64) -> Dt {
270 Dt::new(
271 self.attos
272 .saturating_add((n as i128) * 60 * ATTOS_PER_SEC_I128),
273 self.scale,
274 self.target,
275 )
276 }
277
278 /// Adds the specified number of hours to this time value using saturating arithmetic.
279 #[inline]
280 pub const fn add_hr(&self, n: i64) -> Dt {
281 Dt::new(
282 self.attos
283 .saturating_add((n as i128) * 3600 * ATTOS_PER_SEC_I128),
284 self.scale,
285 self.target,
286 )
287 }
288
289 /// Returns the total time in attoseconds.
290 #[inline(always)]
291 pub const fn to_attos(&self) -> i128 {
292 self.attos
293 }
294
295 /// Returns the total time in milliseconds.
296 #[inline(always)]
297 pub const fn to_ms(&self) -> i128 {
298 self.attos / ATTOS_PER_MS_I128
299 }
300
301 /// Returns the total time in microseconds.
302 #[inline(always)]
303 pub const fn to_us(&self) -> i128 {
304 self.attos / ATTOS_PER_US_I128
305 }
306
307 /// Returns the total time in nanoseconds.
308 #[inline(always)]
309 pub const fn to_ns(&self) -> i128 {
310 self.attos / ATTOS_PER_NS_I128
311 }
312
313 /// Returns the total time in picoseconds.
314 #[inline(always)]
315 pub const fn to_ps(&self) -> i128 {
316 self.attos / ATTOS_PER_PS_I128
317 }
318
319 /// Returns the total time in femtoseconds.
320 #[inline(always)]
321 pub const fn to_fs(&self) -> i128 {
322 self.attos / ATTOS_PER_FS_I128
323 }
324
325 /// Returns `true` if this time is zero.
326 #[inline(always)]
327 pub const fn is_zero(&self) -> bool {
328 self.attos == 0
329 }
330
331 /// Returns `true` if this time is strictly positive **> 0**.
332 #[inline(always)]
333 pub const fn is_positive(&self) -> bool {
334 self.attos > 0
335 }
336
337 /// Multiplies this time by an integer scalar.
338 ///
339 /// Uses 128-bit arithmetic internally.
340 pub const fn mul(self, rhs: i64) -> Dt {
341 if rhs == 0 || self.is_zero() {
342 return Self::ZERO;
343 }
344 let total = self.attos.saturating_mul(rhs as i128);
345 Self::from_attos(total, Scale::TAI)
346 }
347
348 /// Divides this `Dt` by an integer scalar.
349 ///
350 /// Uses truncating division (rounds toward zero), same as normal integer division.
351 /// Returns `ZERO` if `rhs == 0`.
352 pub const fn div(self, rhs: i64) -> Dt {
353 if rhs == 0 || self.is_zero() {
354 return Self::ZERO;
355 }
356 let result = self.attos / (rhs as i128);
357 Self::from_attos(result, Scale::TAI)
358 }
359
360 /// Returns the **largest** multiple of `unit` that is ≤ `self`.
361 /// If `unit` is zero, returns `self` unchanged (exact, full precision).
362 pub const fn floor(&self, unit: Dt) -> Dt {
363 if unit.is_zero() {
364 return *self;
365 }
366 let a = self.attos;
367 let b = unit.attos;
368 let q = safe_div_euc!(a, b, 0i128);
369 let result = q.wrapping_mul(b);
370 Self::from_attos(result, Scale::TAI)
371 }
372
373 /// Returns the **smallest** multiple of `unit` that is ≥ `self`.
374 /// If `unit` is zero, returns `self` unchanged (exact, full precision).
375 pub const fn ceil(&self, unit: Dt) -> Dt {
376 if unit.is_zero() {
377 return *self;
378 }
379 let a = self.attos;
380 let b = unit.attos;
381 // ceil(a/b) ≡ −floor(−a/b)
382 let neg_a = a.wrapping_neg();
383 let q = safe_div_euc!(neg_a, b, 0i128);
384 let q_ceil = q.wrapping_neg();
385 let result = q_ceil.wrapping_mul(b);
386 Self::from_attos(result, Scale::TAI)
387 }
388
389 /// Returns the nearest multiple of `unit`.
390 ///
391 /// Halfway cases round **away from zero** (e.g. `2.5 → 3.0`, `-2.5 → -3.0`),
392 /// matching the behavior of the old `f64::round()`.
393 ///
394 /// - If `unit` is zero, returns `self` unchanged (preserves full precision).
395 /// - Uses Euclidean division internally for correct behavior on negative values.
396 /// - The result is always a multiple of `unit`.
397 pub const fn round(&self, unit: Dt) -> Dt {
398 if unit.is_zero() {
399 return *self;
400 }
401
402 let a = self.attos;
403 let b = unit.attos;
404
405 let abs_a = a.wrapping_abs();
406 let abs_b = b.wrapping_abs();
407
408 let q = safe_div_euc!(abs_a, abs_b, 0i128);
409 let r = safe_rem_euc!(abs_a, abs_b, 0i128);
410
411 let half = (abs_b + 1) / 2;
412
413 let q_rounded = if r >= half { q + 1 } else { q };
414
415 let rounded_abs = q_rounded.wrapping_mul(abs_b);
416
417 let result = if a < 0 { -rounded_abs } else { rounded_abs };
418
419 Self::from_attos(result, Scale::TAI)
420 }
421
422 /// Returns `floor(|self| / |unit|)` as `usize`, saturating at `usize::MAX`.
423 ///
424 /// Fully exact integer arithmetic using 128-bit intermediaries. Used by `TimeRange::len`.
425 pub const fn abs_div_floor(&self, unit: Dt) -> usize {
426 if unit.is_zero() {
427 return 0;
428 }
429 let a = self.attos.wrapping_abs();
430 let b = unit.attos.wrapping_abs();
431 let q = safe_div_euc!(a, b, 0i128);
432
433 if q > (usize::MAX as i128) {
434 usize::MAX
435 } else {
436 q as usize
437 }
438 }
439
440 /// - Integer part of `rhs` is multiplied **exactly** (pure i128 arithmetic).
441 /// - Fractional part (|frac| < 1) uses the 10¹⁵ scaling.
442 pub const fn mul_by_f(&self, rhs: Real) -> Dt {
443 if rhs.is_nan() {
444 return Self::ZERO;
445 }
446 if rhs.is_infinite() {
447 if self.is_zero() {
448 return Self::ZERO;
449 }
450 let self_pos = self.attos > 0;
451 return if (rhs > 0.0) == self_pos {
452 Self::MAX
453 } else {
454 Self::MIN
455 };
456 }
457 if self.is_zero() || rhs == 0.0 {
458 return Self::ZERO;
459 }
460
461 let self_attos = self.attos;
462 let max_attos = Self::MAX.to_attos();
463 let min_attos = Self::MIN.to_attos();
464
465 // Safe extraction of integer part (handles huge |rhs| without UB)
466 let int_part = if rhs >= (i128::MAX as Real) {
467 i128::MAX
468 } else if rhs <= (i128::MIN as Real) {
469 i128::MIN
470 } else {
471 floor_f(rhs) as i128
472 };
473
474 // Huge |rhs| → definitely saturates the type
475 if int_part == i128::MAX || int_part == i128::MIN {
476 let self_pos = self.attos > 0;
477 return if (rhs > 0.0) == self_pos {
478 Self::MAX
479 } else {
480 Self::MIN
481 };
482 }
483
484 let frac_part = rhs - f!(int_part); // always in [0, 1)
485
486 // Integer part with explicit type-range saturation
487 let int_attos = if int_part == 0 {
488 0
489 } else if int_part > 0 {
490 if self_attos > 0 {
491 if int_part > max_attos / self_attos {
492 max_attos
493 } else {
494 self_attos.saturating_mul(int_part)
495 }
496 } else {
497 let abs_self = self_attos.wrapping_neg();
498 let abs_min = min_attos.wrapping_neg();
499 if int_part > abs_min / abs_self {
500 min_attos
501 } else {
502 self_attos.saturating_mul(int_part)
503 }
504 }
505 } else {
506 // int_part < 0
507 if self_attos > 0 {
508 let abs_int = int_part.wrapping_neg();
509 let abs_min = min_attos.wrapping_neg();
510 if abs_int > abs_min / self_attos {
511 min_attos
512 } else {
513 self_attos.saturating_mul(int_part)
514 }
515 } else {
516 let abs_self = self_attos.wrapping_neg();
517 let abs_int = int_part.wrapping_neg();
518 if abs_int > max_attos / abs_self {
519 max_attos
520 } else {
521 self_attos.saturating_mul(int_part)
522 }
523 }
524 };
525
526 // --- Fractional part: decomposed exact computation (never overflows i128) ---
527 const SCALE: i128 = 1_000_000_000_000_000; // 10¹⁵
528 let frac_scaled = (frac_part * (SCALE as Real)) as i128;
529
530 let frac_attos = if self_attos >= 0 {
531 let high = self_attos / SCALE;
532 let low = self_attos % SCALE;
533 let high_part = high * frac_scaled;
534 let low_part = (low * frac_scaled) / SCALE;
535 high_part + low_part
536 } else {
537 let abs_self = self_attos.wrapping_neg();
538 let high = abs_self / SCALE;
539 let low = abs_self % SCALE;
540 let high_part = high * frac_scaled;
541 let low_part = (low * frac_scaled) / SCALE;
542 let pos = high_part + low_part;
543 pos.wrapping_neg()
544 };
545
546 // Combine + final clamp
547 let total_attos = int_attos.saturating_add(frac_attos);
548 let clamped = if total_attos > max_attos {
549 max_attos
550 } else if total_attos < min_attos {
551 min_attos
552 } else {
553 total_attos
554 };
555
556 Self::from_attos(clamped, Scale::TAI)
557 }
558
559 /// Divides by a real number (routes through the high-precision `mul_by_f`).
560 #[inline]
561 pub const fn div_by_f(&self, rhs: Real) -> Dt {
562 if rhs == 0.0 || rhs.is_nan() {
563 return if self.attos >= 0 {
564 Self::MAX
565 } else {
566 Self::MIN
567 };
568 }
569 self.mul_by_f(1.0 / rhs)
570 }
571
572 /// Divides this Dt by 2 (convenience wrapper).
573 #[inline]
574 pub const fn div_by_2(&self) -> Dt {
575 self.div_by_f(2.0)
576 }
577
578 /// Clamps an `i128` to the representable range of `i64`.
579 #[inline(always)]
580 pub(crate) const fn i128_to_i64(x: i128) -> i64 {
581 let y = x as i64;
582 if x == y as i128 {
583 y
584 } else if x > 0 {
585 i64::MAX
586 } else {
587 i64::MIN
588 }
589 }
590
591 /// Converts seconds (i64) → total attoseconds (i128)
592 #[inline(always)]
593 pub const fn sec_to_attos(sec: i128) -> i128 {
594 sec.saturating_mul(ATTOS_PER_SEC_I128)
595 }
596
597 /// Converts total attoseconds → whole seconds as i64
598 #[inline(always)]
599 pub const fn attos_to_sec_i64(attos: i128) -> i64 {
600 Self::i128_to_i64(attos / ATTOS_PER_SEC_I128)
601 }
602
603 /// Clamps `value` to the range `[min, max]`.
604 ///
605 /// This is a `const fn`, so it can be used in const contexts
606 /// (e.g. const generics, statics, const evaluation, etc.).
607 ///
608 /// If `min > max`, the result is equivalent to clamping to `[max, min]`.
609 pub(crate) const fn clamp_u8(value: u8, min: u8, max: u8) -> u8 {
610 if value < min {
611 min
612 } else if value > max {
613 max
614 } else {
615 value
616 }
617 }
618
619 /// Clamps `value` to the range `[min, max]`.
620 ///
621 /// This is a `const fn`, so it can be used in const contexts
622 /// (e.g. const generics, statics, const evaluation, etc.).
623 ///
624 /// If `min > max`, the result is equivalent to clamping to `[max, min]`.
625 pub(crate) const fn clamp_u64(value: u64, min: u64, max: u64) -> u64 {
626 if value < min {
627 min
628 } else if value > max {
629 max
630 } else {
631 value
632 }
633 }
634
635 /// **Lossy** conversion of u128 attoseconds to → float seconds (s).
636 #[inline(always)]
637 pub const fn attos_to_sec_f(attos: u128) -> Real {
638 f!(attos) / ATTOS_PER_SECF
639 }
640
641 /// Converts i128 attoseconds → seconds (s)
642 #[inline(always)]
643 pub const fn attos_to_sec(attos: i128) -> i128 {
644 attos / ATTOS_PER_SEC_I128
645 }
646
647 /// Converts i128 attoseconds → milliseconds (ms)
648 #[inline(always)]
649 pub const fn attos_to_ms(attos: i128) -> i128 {
650 attos / ATTOS_PER_MS_I128
651 }
652
653 /// Converts i128 attoseconds → microseconds (us)
654 #[inline(always)]
655 pub const fn attos_to_us(attos: i128) -> i128 {
656 attos / ATTOS_PER_US_I128
657 }
658
659 /// Converts i128 attoseconds → nanoseconds (ns)
660 #[inline(always)]
661 pub const fn attos_to_ns(attos: i128) -> i128 {
662 attos / ATTOS_PER_NS_I128
663 }
664
665 /// Converts i128 attoseconds → picoseconds (ps)
666 #[inline(always)]
667 pub const fn attos_to_ps(attos: i128) -> i128 {
668 attos / ATTOS_PER_PS_I128
669 }
670
671 /// Converts i128 attoseconds → femtoseconds (fs)
672 #[inline(always)]
673 pub const fn attos_to_fs(attos: i128) -> i128 {
674 attos / ATTOS_PER_FS_I128
675 }
676
677 /// Returns the scalar ratio `self / rhs` expressed in seconds (as `Real`).
678 ///
679 /// This is the floating-point equivalent of `self.to_sec_f() / rhs.to_sec_f()`.
680 ///
681 /// # Special cases (chosen for safety and usability in time arithmetic)
682 /// - `non-zero / ZERO` returns `±Real::INFINITY` (sign matches `self`)
683 /// - `ZERO / non-zero` returns `0.0`
684 /// - `ZERO / ZERO` returns `1.0` (the two durations are identical)
685 ///
686 /// These rules avoid `NaN` entirely while remaining predictable and useful
687 /// in simulations, rate calculations, and control code.
688 ///
689 /// Negative durations are handled correctly (e.g. `(-5 s) / (2 s) == -2.5`).
690 ///
691 /// This method is `const fn` and can be used in const contexts.
692 #[inline]
693 pub const fn div_dt(self, rhs: Dt) -> Real {
694 let a = self.to_sec_f();
695 let b = rhs.to_sec_f();
696
697 if b == 0.0 {
698 if a == 0.0 {
699 1.0
700 } else {
701 Real::INFINITY.copysign(a)
702 }
703 } else {
704 a / b
705 }
706 }
707
708 /// Advances this `Dt` by the given elapsed duration while applying the relativistic proper-time correction
709 /// derived from the supplied `Spacetime` model.
710 ///
711 /// - This method is intended for simulation of remote clocks (e.g., Earth time as observed from a spacecraft).
712 /// - For a local hardware proper-time clock, use the plain `add` methods instead.
713 #[inline]
714 pub const fn adjusted_advance(&mut self, elapsed: &Dt, spacetime: &Spacetime) {
715 let dtau = elapsed.add(Drift::from_spacetime(spacetime).time_diff_after(elapsed));
716 *self = self.add(dtau);
717 }
718
719 /// Advances this `Dt` by the given elapsed duration while applying the relativistic proper-time correction
720 /// from a pre-computed `Drift` value.
721 ///
722 /// - This is an optimized variant of [`Dt::adjusted_advance`](../struct.Dt.html#method.adjusted_advance)
723 /// for callers that already hold a [`Drift`] instance.
724 /// - This method is intended for simulation of remote clocks (e.g., Earth time as observed from a spacecraft).
725 /// - For a local hardware proper-time clock, use the plain `add` methods instead.
726 #[inline]
727 pub const fn adjusted_advance_using_drift(&mut self, elapsed: &Dt, drift: &Drift) {
728 let dtau = elapsed.add(drift.time_diff_after(elapsed));
729 *self = self.add(dtau);
730 }
731}