Skip to main content

deep_time/dt/
arithmetic.rs

1use crate::{
2    ATTOS_PER_FS_I128, ATTOS_PER_HOUR, ATTOS_PER_MIN, ATTOS_PER_MS_I128, ATTOS_PER_NS_I128,
3    ATTOS_PER_PS_I128, ATTOS_PER_SEC_I128, ATTOS_PER_US_I128, Dt, Real, floor_f,
4};
5
6impl Dt {
7    /// Computes the signed duration between this [`Dt`] and another [`Dt`].
8    ///
9    /// Does **not** perform any time scale conversion.
10    #[inline(always)]
11    pub const fn to_diff_raw(&self, other: Dt) -> Dt {
12        Dt::new(
13            self.attos.saturating_sub(other.attos),
14            self.scale,
15            self.target,
16        )
17    }
18
19    /// Computes the signed duration between this [`Dt`] and another [`Dt`] as a float.
20    ///
21    /// Does **not** perform any time scale conversion.
22    #[inline(always)]
23    pub const fn to_diff_raw_f(&self, other: Dt) -> Real {
24        self.to_sec_f() - other.to_sec_f()
25    }
26
27    /// Saturating add, keeps `self`'s `scale` and `target`.
28    ///
29    /// Does **not** perform any time scale conversion.
30    #[inline]
31    pub const fn add(&self, dt: Dt) -> Dt {
32        if !dt.is_zero() {
33            Dt::new(self.attos.saturating_add(dt.attos), self.scale, self.target)
34        } else {
35            *self
36        }
37    }
38
39    /// Saturating sub, keeps `self`'s `scale` and `target`.
40    ///
41    /// Does **not** perform any time scale conversion.
42    #[inline]
43    pub const fn sub(&self, dt: Dt) -> Dt {
44        if !dt.is_zero() {
45            Dt::new(self.attos.saturating_sub(dt.attos), self.scale, self.target)
46        } else {
47            *self
48        }
49    }
50
51    /// Adds the specified number of attoseconds to this time value.
52    ///
53    /// ## Examples
54    ///
55    /// ```rust
56    /// use deep_time::Dt;
57    ///
58    /// let dt = Dt::ZERO;
59    /// let sub_5 = dt.add_attos(-5);
60    /// assert_eq!(sub_5.to_attos(), -5);
61    /// ```
62    #[inline(always)]
63    pub const fn add_attos(&self, n: i128) -> Dt {
64        Dt::new(self.attos.saturating_add(n), self.scale, self.target)
65    }
66
67    /// Adds the specified number of femtoseconds to this time value.
68    ///
69    /// ## Examples
70    ///
71    /// ```rust
72    /// use deep_time::Dt;
73    ///
74    /// let dt = Dt::ZERO;
75    /// let sub_5 = dt.add_fs(-5);
76    /// assert_eq!(sub_5.to_fs().0, -5);
77    /// ```
78    #[inline(always)]
79    pub const fn add_fs(&self, n: i128) -> Dt {
80        self.add_attos(n.saturating_mul(ATTOS_PER_FS_I128))
81    }
82
83    /// Adds the specified number of picoseconds to this time value.
84    ///
85    /// ## Examples
86    ///
87    /// ```rust
88    /// use deep_time::Dt;
89    ///
90    /// let dt = Dt::ZERO;
91    /// let sub_5 = dt.add_ps(-5);
92    /// assert_eq!(sub_5.to_ps().0, -5);
93    /// ```
94    #[inline(always)]
95    pub const fn add_ps(&self, n: i128) -> Dt {
96        self.add_attos(n.saturating_mul(ATTOS_PER_PS_I128))
97    }
98
99    /// Adds the specified number of nanoseconds to this time value.
100    ///
101    /// ## Examples
102    ///
103    /// ```rust
104    /// use deep_time::Dt;
105    ///
106    /// let dt = Dt::ZERO;
107    /// let sub_5 = dt.add_ns(-5);
108    /// assert_eq!(sub_5.to_ns().0, -5);
109    /// ```
110    #[inline(always)]
111    pub const fn add_ns(&self, n: i128) -> Dt {
112        self.add_attos(n.saturating_mul(ATTOS_PER_NS_I128))
113    }
114
115    /// Adds the specified number of microseconds to this time value.
116    ///
117    /// ## Examples
118    ///
119    /// ```rust
120    /// use deep_time::Dt;
121    ///
122    /// let dt = Dt::ZERO;
123    /// let sub_5 = dt.add_us(-5);
124    /// assert_eq!(sub_5.to_us().0, -5);
125    /// ```
126    #[inline(always)]
127    pub const fn add_us(&self, n: i128) -> Dt {
128        self.add_attos(n.saturating_mul(ATTOS_PER_US_I128))
129    }
130
131    /// Adds the specified number of milliseconds to this time value.
132    ///
133    /// ## Examples
134    ///
135    /// ```rust
136    /// use deep_time::Dt;
137    ///
138    /// let dt = Dt::ZERO;
139    /// let sub_5 = dt.add_ms(-5);
140    /// assert_eq!(sub_5.to_ms().0, -5);
141    /// ```
142    #[inline(always)]
143    pub const fn add_ms(&self, n: i128) -> Dt {
144        self.add_attos(n.saturating_mul(ATTOS_PER_MS_I128))
145    }
146
147    /// Adds the specified number of seconds to this time value using saturating arithmetic.
148    ///
149    /// ## Examples
150    ///
151    /// ```rust
152    /// use deep_time::Dt;
153    ///
154    /// let dt = Dt::ZERO;
155    /// let sub_5 = dt.add_sec(-5);
156    /// assert_eq!(sub_5.to_sec(), -5);
157    /// ```
158    #[inline(always)]
159    pub const fn add_sec(&self, n: i128) -> Dt {
160        self.add_attos(n.saturating_mul(ATTOS_PER_SEC_I128))
161    }
162
163    /// Adds the specified number of minutes to this time value using saturating arithmetic.
164    ///
165    /// ## Examples
166    ///
167    /// ```rust
168    /// use deep_time::Dt;
169    ///
170    /// let dt = Dt::ZERO;
171    /// let sub_5 = dt.add_mins(-5);
172    /// assert_eq!(sub_5.to_mins_floor().0, -5);
173    /// ```
174    #[inline(always)]
175    pub const fn add_mins(&self, n: i128) -> Dt {
176        self.add_attos(n.saturating_mul(ATTOS_PER_MIN))
177    }
178
179    /// Adds the specified number of hours to this time value using saturating arithmetic.
180    ///
181    /// ## Examples
182    ///
183    /// ```rust
184    /// use deep_time::Dt;
185    ///
186    /// let dt = Dt::ZERO;
187    /// let sub_5 = dt.add_hours(-5);
188    /// assert_eq!(sub_5.to_hours_floor().0, -5);
189    /// ```
190    #[inline(always)]
191    pub const fn add_hours(&self, n: i128) -> Dt {
192        self.add_attos(n.saturating_mul(ATTOS_PER_HOUR))
193    }
194
195    /// Returns `true` if this time is zero.
196    ///
197    /// Does **not** perform any time scale conversion.
198    #[inline(always)]
199    pub const fn is_zero(&self) -> bool {
200        self.attos == 0
201    }
202
203    /// Returns `true` if this time is strictly positive **> 0**.
204    ///
205    /// Does **not** perform any time scale conversion.
206    #[inline(always)]
207    pub const fn is_positive(&self) -> bool {
208        self.attos > 0
209    }
210
211    /// Multiplies this time by an integer scalar.
212    ///
213    /// Uses 128-bit arithmetic internally.
214    pub const fn mul(self, rhs: i64) -> Dt {
215        if rhs == 0 || self.is_zero() {
216            return Self::ZERO;
217        }
218        let total = self.attos.saturating_mul(rhs as i128);
219        Dt::new(total, self.scale, self.target)
220    }
221
222    /// Divides this `Dt` by an integer scalar.
223    ///
224    /// Uses truncating division (rounds toward zero), same as normal integer division.
225    /// Returns `ZERO` if `rhs == 0`.
226    pub const fn div(self, rhs: i64) -> Dt {
227        if rhs == 0 || self.is_zero() {
228            return Self::ZERO;
229        }
230        let result = self.attos / (rhs as i128);
231        Dt::new(result, self.scale, self.target)
232    }
233
234    /// Returns the **largest** multiple of `unit` that is ≤ `self`.
235    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
236    pub const fn floor(&self, unit: Dt) -> Dt {
237        if unit.is_zero() {
238            return *self;
239        }
240        let a = self.attos;
241        let b = unit.attos;
242        let q = safe_div_euc!(a, b, 0i128);
243        let result = q.wrapping_mul(b);
244        Dt::new(result, self.scale, self.target)
245    }
246
247    /// Returns the **smallest** multiple of `unit` that is ≥ `self`.
248    /// If `unit` is zero, returns `self` unchanged (exact, full precision).
249    pub const fn ceil(&self, unit: Dt) -> Dt {
250        if unit.is_zero() {
251            return *self;
252        }
253        let a = self.attos;
254        let b = unit.attos;
255        // ceil(a/b) ≡ −floor(−a/b)
256        let neg_a = a.wrapping_neg();
257        let q = safe_div_euc!(neg_a, b, 0i128);
258        let q_ceil = q.wrapping_neg();
259        let result = q_ceil.wrapping_mul(b);
260        Dt::new(result, self.scale, self.target)
261    }
262
263    /// ## Examples
264    ///
265    /// ```rust
266    /// use deep_time::{Dt, TimeTraits};
267    ///
268    /// // Round to nearest second
269    /// let dt = 1.3.sec();
270    /// assert_eq!(dt.round(1.sec()), 1.sec());
271    ///
272    /// let dt = 1.6.sec();
273    /// assert_eq!(dt.round(1.sec()), 2.sec());
274    ///
275    /// // Negative values
276    /// let dt = (-1.3).sec();
277    /// assert_eq!(dt.round(1.sec()), (-1).sec());
278    ///
279    /// // Halfway cases round *away from zero*
280    /// assert_eq!(0.5.sec().round(1.sec()), 1.sec());
281    /// assert_eq!((-0.5).sec().round(1.sec()), (-1).sec());
282    ///
283    /// assert_eq!(1.5.sec().round(1.sec()), 2.sec());
284    /// assert_eq!((-1.5).sec().round(1.sec()), (-2).sec());
285    ///
286    /// // Round to nearest minute
287    /// let dt = (1.mins() + 40.sec()).round(1.mins());
288    /// assert_eq!(dt, 2.mins());
289    ///
290    /// // Round to nearest hour
291    /// let dt = 1.6.hours().round(1.hours());
292    /// assert_eq!(dt, 2.hours());
293    /// ```
294    pub const fn round(&self, unit: Dt) -> Dt {
295        if unit.is_zero() {
296            return *self;
297        }
298
299        let a = self.attos;
300        let b = unit.attos;
301
302        let abs_a = a.wrapping_abs();
303        let abs_b = b.wrapping_abs();
304
305        let q = safe_div_euc!(abs_a, abs_b, 0i128);
306        let r = safe_rem_euc!(abs_a, abs_b, 0i128);
307
308        let half = (abs_b + 1) / 2;
309
310        let q_rounded = if r >= half { q + 1 } else { q };
311
312        let rounded_abs = q_rounded.wrapping_mul(abs_b);
313
314        let result = if a < 0 { -rounded_abs } else { rounded_abs };
315
316        Dt::new(result, self.scale, self.target)
317    }
318
319    /// Returns `floor(|self| / |unit|)` as `usize`, saturating at `usize::MAX`.
320    ///
321    /// Fully exact integer arithmetic using 128-bit intermediaries. Used by `TimeRange::len`.
322    pub const fn abs_div_floor(&self, unit: Dt) -> usize {
323        if unit.is_zero() {
324            return 0;
325        }
326        let a = self.attos.wrapping_abs();
327        let b = unit.attos.wrapping_abs();
328        let q = safe_div_euc!(a, b, 0i128);
329
330        if q > (usize::MAX as i128) {
331            usize::MAX
332        } else {
333            q as usize
334        }
335    }
336
337    /// Multiplies this [`Dt`] by a floating-point scalar using saturating attosecond arithmetic.
338    ///
339    /// ## Algorithm
340    ///
341    /// - `rhs` is split into an **integer part** ([`floor_f`]) and a **fractional part** in `[0, 1)`.
342    /// - The integer part is multiplied exactly via [`i128::checked_mul`], saturating to
343    ///   [`Dt::MAX`] / [`Dt::MIN`] on overflow.
344    /// - The fractional part is applied via a `10¹⁵`-scaled decomposition that avoids
345    ///   intermediate `i128` overflow.
346    /// - The two parts are combined with [`i128::saturating_add`] and clamped to the
347    ///   representable attosecond range.
348    ///
349    /// ## Precision
350    ///
351    /// - Integer scalars (e.g. `2.0`, `-3.0`) use exact integer arithmetic for their whole part.
352    /// - General `f64` scalars are limited by IEEE-754 precision (~15 decimal digits) and the
353    ///   `10¹⁵` fractional quantization.
354    ///
355    /// ## Special cases
356    ///
357    /// | Condition | Result |
358    /// |---|---|
359    /// | `rhs` is NaN | [`Dt::ZERO`] |
360    /// | `rhs` is ±∞ and `self` is zero | [`Dt::ZERO`] |
361    /// | `rhs` is ±∞ and `self` is non-zero | [`Dt::MAX`] or [`Dt::MIN`] (sign of product) |
362    /// | `rhs == 0.0` or `self` is zero | [`Dt::ZERO`] |
363    /// | Product exceeds `i128` range | [`Dt::MAX`] or [`Dt::MIN`] (sign of product) |
364    ///
365    /// `NaN` maps to zero rather than poisoning the result: [`Dt`] has no NaN state, and zero
366    /// is the additive identity (a safe, non-saturating default for invalid scale factors).
367    pub const fn mul_by_f(&self, rhs: Real) -> Dt {
368        if rhs.is_nan() {
369            return Self::ZERO;
370        }
371        if rhs.is_infinite() {
372            if self.is_zero() {
373                return Self::ZERO;
374            }
375            let self_pos = self.attos > 0;
376            return if (rhs > 0.0) == self_pos {
377                Self::MAX
378            } else {
379                Self::MIN
380            };
381        }
382        if self.is_zero() || rhs == 0.0 {
383            return Self::ZERO;
384        }
385
386        let self_attos = self.attos;
387        let max_attos = Self::MAX.to_attos();
388        let min_attos = Self::MIN.to_attos();
389
390        // Safe extraction of integer part (handles huge |rhs| without UB)
391        let int_part = if rhs >= (i128::MAX as Real) {
392            i128::MAX
393        } else if rhs <= (i128::MIN as Real) {
394            i128::MIN
395        } else {
396            floor_f(rhs) as i128
397        };
398
399        // Huge |rhs| integer → product cannot fit; saturate immediately.
400        if int_part == i128::MAX || int_part == i128::MIN {
401            let self_pos = self.attos > 0;
402            return if (rhs > 0.0) == self_pos {
403                Self::MAX
404            } else {
405                Self::MIN
406            };
407        }
408
409        let frac_part = rhs - f!(int_part); // always in [0, 1)
410
411        let int_attos = if int_part == 0 {
412            0
413        } else {
414            Self::saturating_mul_attos(int_part, self_attos, max_attos, min_attos)
415        };
416
417        // Fractional part: decomposed exact computation (never overflows i128)
418        const SCALE: i128 = 1_000_000_000_000_000; // 10¹⁵
419        let frac_scaled = (frac_part * (SCALE as Real)) as i128;
420
421        let frac_attos = if self_attos >= 0 {
422            let high = self_attos / SCALE;
423            let low = self_attos % SCALE;
424            let high_part = high * frac_scaled;
425            let low_part = (low * frac_scaled) / SCALE;
426            high_part + low_part
427        } else {
428            let abs_self = self_attos.wrapping_neg();
429            let high = abs_self / SCALE;
430            let low = abs_self % SCALE;
431            let high_part = high * frac_scaled;
432            let low_part = (low * frac_scaled) / SCALE;
433            let pos = high_part + low_part;
434            pos.wrapping_neg()
435        };
436
437        let total_attos = int_attos.saturating_add(frac_attos);
438        let clamped = if total_attos > max_attos {
439            max_attos
440        } else if total_attos < min_attos {
441            min_attos
442        } else {
443            total_attos
444        };
445
446        Dt::new(clamped, self.scale, self.target)
447    }
448
449    /// `a * b` as attoseconds, saturating to `[min_attos, max_attos]` when not representable.
450    #[inline(always)]
451    pub(crate) const fn saturating_mul_attos(
452        a: i128,
453        b: i128,
454        max_attos: i128,
455        min_attos: i128,
456    ) -> i128 {
457        match a.checked_mul(b) {
458            Some(product) => product,
459            None => {
460                let a_neg = a < 0;
461                let b_neg = b < 0;
462                if a_neg == b_neg { max_attos } else { min_attos }
463            }
464        }
465    }
466
467    /// Divides by a real number (routes through the high-precision `mul_by_f`).
468    #[inline]
469    pub const fn div_by_f(&self, rhs: Real) -> Dt {
470        if rhs == 0.0 || rhs.is_nan() {
471            return if self.attos >= 0 {
472                Self::MAX
473            } else {
474                Self::MIN
475            };
476        }
477        self.mul_by_f(1.0 / rhs)
478    }
479
480    /// Divides this Dt by 2 (convenience wrapper).
481    #[inline]
482    pub const fn div_by_2(&self) -> Dt {
483        self.div_by_f(2.0)
484    }
485
486    /// Returns the scalar ratio `self / rhs` expressed in seconds (as `Real`).
487    ///
488    /// This is the floating-point equivalent of `self.to_sec_f() / rhs.to_sec_f()`.
489    ///
490    /// # Special cases (chosen for safety and usability in time arithmetic)
491    /// - `non-zero / ZERO` returns `±Real::INFINITY` (sign matches `self`)
492    /// - `ZERO / non-zero` returns `0.0`
493    /// - `ZERO / ZERO` returns `1.0` (the two durations are identical)
494    ///
495    /// These rules avoid `NaN` entirely while remaining predictable and useful
496    /// in simulations, rate calculations, and control code.
497    ///
498    /// Negative durations are supported (e.g. `(-5 s) / (2 s) == -2.5`).
499    ///
500    /// This method is `const fn` and can be used in const contexts.
501    #[inline]
502    pub const fn div_dt(self, rhs: Dt) -> Real {
503        let a = self.to_sec_f();
504        let b = rhs.to_sec_f();
505
506        if b == 0.0 {
507            if a == 0.0 {
508                1.0
509            } else {
510                Real::INFINITY.copysign(a)
511            }
512        } else {
513            a / b
514        }
515    }
516}