Coverage Report

Created: 2026-05-16 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/hifitime-4.3.0/src/duration/ops.rs
Line
Count
Source
1
/*
2
* Hifitime
3
* Copyright (C) 2017-onward Christopher Rabotin <christopher.rabotin@gmail.com> et al. (cf. https://github.com/nyx-space/hifitime/graphs/contributors)
4
* This Source Code Form is subject to the terms of the Mozilla Public
5
* License, v. 2.0. If a copy of the MPL was not distributed with this
6
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
*
8
* Documentation: https://nyxspace.com/
9
*/
10
11
// Here lives all of the operations on Duration.
12
13
use crate::{
14
    NANOSECONDS_PER_CENTURY, NANOSECONDS_PER_MICROSECOND, NANOSECONDS_PER_MILLISECOND,
15
    NANOSECONDS_PER_SECOND,
16
};
17
18
use super::{Duration, Freq, Frequencies, TimeUnits, Unit};
19
20
use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
21
22
#[cfg(not(feature = "std"))]
23
#[allow(unused_imports)] // Import is indeed used.
24
use num_traits::Float;
25
26
macro_rules! impl_ops_for_type {
27
    ($type:ident) => {
28
        impl Mul<Unit> for $type {
29
            type Output = Duration;
30
145k
            fn mul(self, q: Unit) -> Duration {
31
                // Apply the reflexive property
32
145k
                q * self
33
145k
            }
<f64 as core::ops::arith::Mul<hifitime::timeunits::Unit>>::mul
Line
Count
Source
30
95.1k
            fn mul(self, q: Unit) -> Duration {
31
                // Apply the reflexive property
32
95.1k
                q * self
33
95.1k
            }
<i64 as core::ops::arith::Mul<hifitime::timeunits::Unit>>::mul
Line
Count
Source
30
50.6k
            fn mul(self, q: Unit) -> Duration {
31
                // Apply the reflexive property
32
50.6k
                q * self
33
50.6k
            }
34
        }
35
36
        impl Mul<$type> for Freq {
37
            type Output = Duration;
38
39
            /// Converts the input values to i128 and creates a duration from that
40
            /// This method will necessarily ignore durations below nanoseconds
41
0
            fn mul(self, q: $type) -> Duration {
42
0
                let total_ns = match self {
43
0
                    Freq::GigaHertz => 1.0 / (q as f64),
44
0
                    Freq::MegaHertz => (NANOSECONDS_PER_MICROSECOND as f64) / (q as f64),
45
0
                    Freq::KiloHertz => NANOSECONDS_PER_MILLISECOND as f64 / (q as f64),
46
0
                    Freq::Hertz => (NANOSECONDS_PER_SECOND as f64) / (q as f64),
47
                };
48
0
                if total_ns.abs() < (i64::MAX as f64) {
49
0
                    Duration::from_truncated_nanoseconds(total_ns as i64)
50
                } else {
51
0
                    Duration::from_total_nanoseconds(total_ns as i128)
52
                }
53
0
            }
Unexecuted instantiation: <hifitime::timeunits::Freq as core::ops::arith::Mul<f64>>::mul
Unexecuted instantiation: <hifitime::timeunits::Freq as core::ops::arith::Mul<i64>>::mul
54
        }
55
56
        impl Mul<Freq> for $type {
57
            type Output = Duration;
58
0
            fn mul(self, q: Freq) -> Duration {
59
                // Apply the reflexive property
60
0
                q * self
61
0
            }
Unexecuted instantiation: <f64 as core::ops::arith::Mul<hifitime::timeunits::Freq>>::mul
Unexecuted instantiation: <i64 as core::ops::arith::Mul<hifitime::timeunits::Freq>>::mul
62
        }
63
64
        #[allow(clippy::suspicious_arithmetic_impl)]
65
        impl Div<$type> for Duration {
66
            type Output = Duration;
67
0
            fn div(self, q: $type) -> Self::Output {
68
0
                Duration::from_total_nanoseconds(
69
0
                    self.total_nanoseconds()
70
0
                        .saturating_div((q * Unit::Nanosecond).total_nanoseconds()),
71
                )
72
0
            }
Unexecuted instantiation: <hifitime::duration::Duration as core::ops::arith::Div<f64>>::div
Unexecuted instantiation: <hifitime::duration::Duration as core::ops::arith::Div<i64>>::div
73
        }
74
75
        impl Mul<Duration> for $type {
76
            type Output = Duration;
77
0
            fn mul(self, q: Self::Output) -> Self::Output {
78
                // Apply the reflexive property
79
0
                q * self
80
0
            }
Unexecuted instantiation: <f64 as core::ops::arith::Mul<hifitime::duration::Duration>>::mul
Unexecuted instantiation: <i64 as core::ops::arith::Mul<hifitime::duration::Duration>>::mul
81
        }
82
83
        impl TimeUnits for $type {}
84
85
        impl Frequencies for $type {}
86
    };
87
}
88
89
impl_ops_for_type!(f64);
90
impl_ops_for_type!(i64);
91
92
impl Mul<i64> for Duration {
93
    type Output = Duration;
94
0
    fn mul(self, q: i64) -> Self::Output {
95
0
        Duration::from_total_nanoseconds(
96
0
            self.total_nanoseconds()
97
0
                .saturating_mul((q * Unit::Nanosecond).total_nanoseconds()),
98
        )
99
0
    }
100
}
101
102
impl Mul<f64> for Duration {
103
    type Output = Duration;
104
0
    fn mul(self, q: f64) -> Self::Output {
105
        // Make sure that we don't trim the number by finding its precision
106
0
        let mut p: i32 = 0;
107
0
        let mut new_val: f64 = q;
108
0
        let ten: f64 = 10.0;
109
110
        // Loop invariant: p stays in [0, 19] across all iterations.
111
        // Decreases clause: 19 - p strictly decreases each iteration (p increments by 1),
112
        // proving termination. Together they establish total correctness:
113
        // the loop terminates with p ∈ [0, 19] for all f64 inputs.
114
        //
115
        // The while condition consolidates the two break conditions from the original
116
        // loop { if ... break; ... if p >= 19 break; } into a single guard:
117
        //   - !new_val.is_finite(): breaks when q * 10^p overflows to infinity/NaN
118
        //   - floor check: breaks when new_val is an integer (precision found)
119
        //   - p < 19: breaks when f64's ~17 significant digits are exhausted
120
        #[cfg_attr(kani, kani::loop_invariant(p >= 0 && p <= 19))]
121
        // TODO: enable when Kani supports loop_decreases (PR #4564)
122
        // #[cfg_attr(kani, kani::loop_decreases(19i32.wrapping_sub(p)))]
123
0
        while new_val.is_finite() && (new_val.floor() - new_val).abs() >= f64::EPSILON && p < 19 {
124
0
            p += 1;
125
0
            new_val = q * ten.powi(p);
126
0
        }
127
128
        // If new_val overflowed to infinity (e.g., very large q), the cast
129
        // `inf as i128` is undefined behavior. Handle it explicitly.
130
0
        if !new_val.is_finite() {
131
0
            if q.is_sign_negative() {
132
0
                return Duration::MIN;
133
            } else {
134
0
                return Duration::MAX;
135
            }
136
0
        }
137
138
0
        Duration::from_total_nanoseconds(
139
0
            self.total_nanoseconds()
140
0
                .saturating_mul(new_val as i128)
141
0
                .saturating_div(10_i128.pow(p.try_into().unwrap())),
142
        )
143
0
    }
144
}
145
146
impl Add for Duration {
147
    type Output = Duration;
148
149
    /// # Addition of Durations
150
    /// Durations are centered on zero duration. Of the tuple, only the centuries may be negative, the nanoseconds are always positive
151
    /// and represent the nanoseconds _into_ the current centuries.
152
    ///
153
    /// ## Examples
154
    /// + `Duration { centuries: 0, nanoseconds: 1 }` is a positive duration of zero centuries and one nanosecond.
155
    /// + `Duration { centuries: -1, nanoseconds: 1 }` is a negative duration representing "one century before zero minus one nanosecond"
156
    #[allow(clippy::absurd_extreme_comparisons)]
157
209M
    fn add(mut self, mut rhs: Self) -> Duration {
158
        // Ensure that the durations are normalized to avoid extra logic to handle under/overflows
159
209M
        self.normalize();
160
209M
        rhs.normalize();
161
162
        // Check that the addition fits in an i16
163
209M
        match self.centuries.checked_add(rhs.centuries) {
164
            None => {
165
                // Overflowed, so we've hit the bound.
166
0
                if self.centuries < 0 {
167
                    // We've hit the negative bound, so return MIN.
168
0
                    return Self::MIN;
169
                } else {
170
                    // We've hit the positive bound, so return MAX.
171
0
                    return Self::MAX;
172
                }
173
            }
174
209M
            Some(centuries) => {
175
209M
                self.centuries = centuries;
176
209M
            }
177
        }
178
179
209M
        if self.centuries == Self::MIN.centuries && self.nanoseconds < Self::MIN.nanoseconds {
180
            // Then we do the operation backward
181
0
            match self
182
0
                .nanoseconds
183
0
                .checked_sub(NANOSECONDS_PER_CENTURY - rhs.nanoseconds)
184
            {
185
0
                Some(nanos) => self.nanoseconds = nanos,
186
                None => {
187
0
                    self.centuries += 1; // Safe because we're at the MIN
188
0
                    self.nanoseconds = rhs.nanoseconds
189
                }
190
            }
191
        } else {
192
209M
            match self.nanoseconds.checked_add(rhs.nanoseconds) {
193
209M
                Some(nanoseconds) => self.nanoseconds = nanoseconds,
194
                None => {
195
                    // Rare case where somehow the input data was not normalized. So let's normalize it and call add again.
196
0
                    let mut rhs = rhs;
197
0
                    rhs.normalize();
198
199
0
                    match self.centuries.checked_add(rhs.centuries) {
200
0
                        None => return Self::MAX,
201
0
                        Some(centuries) => self.centuries = centuries,
202
                    };
203
                    // Now it will fit!
204
0
                    self.nanoseconds += rhs.nanoseconds;
205
                }
206
            }
207
        }
208
209
209M
        self.normalize();
210
209M
        self
211
209M
    }
212
}
213
214
impl AddAssign for Duration {
215
8.33k
    fn add_assign(&mut self, rhs: Duration) {
216
8.33k
        *self = *self + rhs;
217
8.33k
    }
218
}
219
220
impl Sub for Duration {
221
    type Output = Self;
222
223
    /// # Subtraction
224
    /// This operation is a notch confusing with negative durations.
225
    /// As described in the `Duration` structure, a Duration of (-1, NANOSECONDS_PER_CENTURY-1) is closer to zero
226
    /// than (-1, 0).
227
    ///
228
    /// ## Algorithm
229
    ///
230
    /// ### A > B, and both are positive
231
    ///
232
    /// If A > B, then A.centuries is subtracted by B.centuries, and A.nanoseconds is subtracted by B.nanoseconds.
233
    /// If an overflow occurs, e.g. A.nanoseconds < B.nanoseconds, the number of nanoseconds is increased by the number of nanoseconds per century,
234
    /// and the number of centuries is decreased by one.
235
    ///
236
    /// ```
237
    /// use hifitime::{Duration, NANOSECONDS_PER_CENTURY};
238
    ///
239
    /// let a = Duration::from_parts(1, 1);
240
    /// let b = Duration::from_parts(0, 10);
241
    /// let c = Duration::from_parts(0, NANOSECONDS_PER_CENTURY - 9);
242
    /// assert_eq!(a - b, c);
243
    /// ```
244
    ///
245
    /// ### A < B, and both are positive
246
    ///
247
    /// In this case, the resulting duration will be negative. The number of centuries is a signed integer, so it is set to the difference of A.centuries - B.centuries.
248
    /// The number of nanoseconds however must be wrapped by the number of nanoseconds per century.
249
    /// For example:, let A = (0, 1) and B = (1, 10), then the resulting duration will be (-2, NANOSECONDS_PER_CENTURY - (10 - 1)). In this case, the centuries are set
250
    /// to -2 because B is _two_ centuries into the future (the number of centuries into the future is zero-indexed).
251
    /// ```
252
    /// use hifitime::{Duration, NANOSECONDS_PER_CENTURY};
253
    ///
254
    /// let a = Duration::from_parts(0, 1);
255
    /// let b = Duration::from_parts(1, 10);
256
    /// let c = Duration::from_parts(-2, NANOSECONDS_PER_CENTURY - 9);
257
    /// assert_eq!(a - b, c);
258
    /// ```
259
    ///
260
    /// ### A > B, both are negative
261
    ///
262
    /// In this case, we try to stick to normal arithmatics: (-9 - -10) = (-9 + 10) = +1.
263
    /// In this case, we can simply add the components of the duration together.
264
    /// For example, let A = (-1, NANOSECONDS_PER_CENTURY - 2), and B = (-1, NANOSECONDS_PER_CENTURY - 1). Respectively, A is _two_ nanoseconds _before_ Duration::ZERO
265
    /// and B is _one_ nanosecond before Duration::ZERO. Then, A-B should be one nanoseconds before zero, i.e. (-1, NANOSECONDS_PER_CENTURY - 1).
266
    /// This is because we _subtract_ "negative one nanosecond" from a "negative minus two nanoseconds", which corresponds to _adding_ the opposite, and the
267
    /// opposite of "negative one nanosecond" is "positive one nanosecond".
268
    ///
269
    /// ```
270
    /// use hifitime::{Duration, NANOSECONDS_PER_CENTURY};
271
    ///
272
    /// let a = Duration::from_parts(-1, NANOSECONDS_PER_CENTURY - 9);
273
    /// let b = Duration::from_parts(-1, NANOSECONDS_PER_CENTURY - 10);
274
    /// let c = Duration::from_parts(0, 1);
275
    /// assert_eq!(a - b, c);
276
    /// ```
277
    ///
278
    /// ### A < B, both are negative
279
    ///
280
    /// Just like in the prior case, we try to stick to normal arithmatics: (-10 - -9) = (-10 + 9) = -1.
281
    ///
282
    /// ```
283
    /// use hifitime::{Duration, NANOSECONDS_PER_CENTURY};
284
    ///
285
    /// let a = Duration::from_parts(-1, NANOSECONDS_PER_CENTURY - 10);
286
    /// let b = Duration::from_parts(-1, NANOSECONDS_PER_CENTURY - 9);
287
    /// let c = Duration::from_parts(-1, NANOSECONDS_PER_CENTURY - 1);
288
    /// assert_eq!(a - b, c);
289
    /// ```
290
    ///
291
    /// ### MIN is the minimum
292
    ///
293
    /// One cannot subtract anything from the MIN.
294
    ///
295
    /// ```
296
    /// use hifitime::Duration;
297
    ///
298
    /// let one_ns = Duration::from_parts(0, 1);
299
    /// assert_eq!(Duration::MIN - one_ns, Duration::MIN);
300
    /// ```
301
1.00M
    fn sub(mut self, mut rhs: Self) -> Self {
302
        // Ensure that the durations are normalized to avoid extra logic to handle under/overflows
303
1.00M
        self.normalize();
304
1.00M
        rhs.normalize();
305
1.00M
        match self.centuries.checked_sub(rhs.centuries) {
306
            None => {
307
                // Underflowed, so we've hit the min
308
0
                return Self::MIN;
309
            }
310
1.00M
            Some(centuries) => {
311
1.00M
                self.centuries = centuries;
312
1.00M
            }
313
        }
314
315
1.00M
        match self.nanoseconds.checked_sub(rhs.nanoseconds) {
316
            None => {
317
                // Decrease the number of centuries, and realign
318
15.9k
                match self.centuries.checked_sub(1) {
319
11.9k
                    Some(centuries) => {
320
11.9k
                        self.centuries = centuries;
321
11.9k
                        self.nanoseconds += NANOSECONDS_PER_CENTURY - rhs.nanoseconds;
322
11.9k
                    }
323
                    None => {
324
                        // We're at the min number of centuries already, and we have extra nanos, so we're saturated the duration limit
325
4.07k
                        return Self::MIN;
326
                    }
327
                };
328
            }
329
993k
            Some(nanos) => self.nanoseconds = nanos,
330
        };
331
332
1.00M
        self.normalize();
333
1.00M
        self
334
1.00M
    }
335
}
336
337
impl SubAssign for Duration {
338
29.1k
    fn sub_assign(&mut self, rhs: Self) {
339
29.1k
        *self = *self - rhs;
340
29.1k
    }
341
}
342
343
// Allow adding with a Unit directly
344
impl Add<Unit> for Duration {
345
    type Output = Self;
346
347
    #[allow(clippy::identity_op)]
348
0
    fn add(self, rhs: Unit) -> Self {
349
0
        self + rhs * 1
350
0
    }
351
}
352
353
impl AddAssign<Unit> for Duration {
354
    #[allow(clippy::identity_op)]
355
208M
    fn add_assign(&mut self, rhs: Unit) {
356
208M
        *self = *self + rhs * 1;
357
208M
    }
358
}
359
360
impl Sub<Unit> for Duration {
361
    type Output = Duration;
362
363
    #[allow(clippy::identity_op)]
364
0
    fn sub(self, rhs: Unit) -> Duration {
365
0
        self - rhs * 1
366
0
    }
367
}
368
369
impl SubAssign<Unit> for Duration {
370
    #[allow(clippy::identity_op)]
371
925k
    fn sub_assign(&mut self, rhs: Unit) {
372
925k
        *self = *self - rhs * 1;
373
925k
    }
374
}
375
376
impl Neg for Duration {
377
    type Output = Self;
378
379
4.34k
    fn neg(self) -> Self::Output {
380
4.34k
        if self == Self::MIN {
381
0
            Self::MAX
382
4.34k
        } else if self == Self::MAX {
383
0
            Self::MIN
384
        } else {
385
4.34k
            let centuries = -i32::from(self.centuries) - 1;
386
4.34k
            let nanoseconds = NANOSECONDS_PER_CENTURY - self.nanoseconds;
387
4.34k
            Self::from_parts(
388
4.34k
                i16::try_from(centuries).expect("negated duration centuries must fit in i16"),
389
4.34k
                nanoseconds,
390
            )
391
        }
392
4.34k
    }
393
}