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