Skip to main content

lox_core/time/
subsecond.rs

1// SPDX-FileCopyrightText: 2023 Andrei Zisu <matzipan@gmail.com>
2// SPDX-FileCopyrightText: 2023 Angus Morrison <github@angus-morrison.com>
3// SPDX-FileCopyrightText: 2023 Helge Eichhorn <git@helgeeichhorn.de>
4//
5// SPDX-License-Identifier: MPL-2.0
6
7//! The [Subsecond] newtype for working with fractions of seconds.
8//!
9//! This module provides a high-precision representation of subsecond time values
10//! with attosecond (10⁻¹⁸ second) resolution. The representation uses six components
11//! to store milliseconds, microseconds, nanoseconds, picoseconds, femtoseconds, and
12//! attoseconds, each normalized to the range [0, 999].
13//!
14//! # Examples
15//!
16//! ```
17//! use lox_core::time::subsecond::Subsecond;
18//!
19//! // Create from individual components
20//! let s = Subsecond::new()
21//!     .set_milliseconds(123)
22//!     .set_microseconds(456)
23//!     .set_nanoseconds(789);
24//!
25//! assert_eq!(s.as_attoseconds(), 123456789000000000);
26//!
27//! // Create from total attoseconds
28//! let s = Subsecond::from_attoseconds(123456789123456789);
29//! assert_eq!(s.milliseconds(), 123);
30//! assert_eq!(s.microseconds(), 456);
31//!
32//! // Parse from string
33//! let s: Subsecond = "123456".parse().unwrap();
34//! assert_eq!(s.milliseconds(), 123);
35//! assert_eq!(s.microseconds(), 456);
36//! ```
37
38use std::fmt::Display;
39use std::str::FromStr;
40
41use crate::f64::consts::SECONDS_PER_ATTOSECOND;
42use crate::i64::consts::{
43    ATTOSECONDS_IN_FEMTOSECOND, ATTOSECONDS_IN_MICROSECOND, ATTOSECONDS_IN_MILLISECOND,
44    ATTOSECONDS_IN_NANOSECOND, ATTOSECONDS_IN_PICOSECOND, ATTOSECONDS_IN_SECOND,
45};
46use thiserror::Error;
47
48const FACTORS: [i64; 6] = [
49    ATTOSECONDS_IN_MILLISECOND,
50    ATTOSECONDS_IN_MICROSECOND,
51    ATTOSECONDS_IN_NANOSECOND,
52    ATTOSECONDS_IN_PICOSECOND,
53    ATTOSECONDS_IN_FEMTOSECOND,
54    1,
55];
56
57/// A high-precision representation of subsecond time with attosecond resolution.
58///
59/// `Subsecond` stores time values less than one second using six components:
60/// milliseconds, microseconds, nanoseconds, picoseconds, femtoseconds, and attoseconds.
61/// Each component is normalized to the range [0, 999].
62///
63/// The total precision is 10⁻¹⁸ seconds (one attosecond), providing sufficient accuracy
64/// for astronomical and high-precision timing applications.
65#[derive(Debug, Default, Clone, Copy)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67pub struct Subsecond([u32; 6]);
68
69impl Subsecond {
70    /// A constant representing zero subsecond time.
71    pub const ZERO: Self = Self::new();
72
73    /// Creates a new `Subsecond` with all components set to zero.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use lox_core::time::subsecond::Subsecond;
79    ///
80    /// let s = Subsecond::new();
81    /// assert_eq!(s.as_attoseconds(), 0);
82    /// ```
83    pub const fn new() -> Self {
84        Self([0; 6])
85    }
86
87    /// Creates a `Subsecond` from a total number of attoseconds.
88    ///
89    /// The input value is automatically normalized to the range [0, 10¹⁸) attoseconds
90    /// (i.e., [0, 1) seconds). Values greater than or equal to one second wrap around,
91    /// and negative values wrap from the top.
92    ///
93    /// # Examples
94    ///
95    /// ```
96    /// use lox_core::time::subsecond::Subsecond;
97    ///
98    /// let s = Subsecond::from_attoseconds(123456789123456789);
99    /// assert_eq!(s.milliseconds(), 123);
100    /// assert_eq!(s.microseconds(), 456);
101    /// assert_eq!(s.nanoseconds(), 789);
102    ///
103    /// // Negative values wrap around
104    /// let s = Subsecond::from_attoseconds(-1);
105    /// assert_eq!(s.as_attoseconds(), 999999999999999999);
106    /// ```
107    pub const fn from_attoseconds(attoseconds: i64) -> Self {
108        let attoseconds_normalized = if attoseconds < 0 {
109            ATTOSECONDS_IN_SECOND + attoseconds
110        } else {
111            attoseconds
112        } as i128;
113        let mut this = Self::new();
114        this.0[0] = ((attoseconds_normalized / ATTOSECONDS_IN_MILLISECOND as i128) % 1000) as u32;
115        this.0[1] = ((attoseconds_normalized / ATTOSECONDS_IN_MICROSECOND as i128) % 1000) as u32;
116        this.0[2] = ((attoseconds_normalized / ATTOSECONDS_IN_NANOSECOND as i128) % 1000) as u32;
117        this.0[3] = ((attoseconds_normalized / ATTOSECONDS_IN_PICOSECOND as i128) % 1000) as u32;
118        this.0[4] = ((attoseconds_normalized / ATTOSECONDS_IN_FEMTOSECOND as i128) % 1000) as u32;
119        this.0[5] = (attoseconds_normalized % 1000) as u32;
120        this
121    }
122
123    pub const fn from_f64(value: f64) -> Option<Self> {
124        if !value.is_finite() {
125            return None;
126        }
127        let rem = value % 1.0;
128        // Ensure remainder is in [0, 1) range (Rust's % can return negative values)
129        let rem = if rem < 0.0 { rem + 1.0 } else { rem };
130        // Convert to attoseconds with rounding to handle floating-point precision issues
131        let attoseconds = (rem / crate::f64::consts::SECONDS_PER_ATTOSECOND).round() as i64;
132        Some(Self::from_attoseconds(attoseconds))
133    }
134
135    /// Sets the millisecond component (10⁻³ seconds).
136    ///
137    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
138    /// In debug builds, values >= 1000 trigger an assertion.
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use lox_core::time::subsecond::Subsecond;
144    ///
145    /// let s = Subsecond::new().set_milliseconds(123);
146    /// assert_eq!(s.milliseconds(), 123);
147    /// ```
148    pub const fn set_milliseconds(mut self, milliseconds: u32) -> Self {
149        debug_assert!(milliseconds < 1000);
150        self.0[0] = milliseconds % 1000;
151        self
152    }
153
154    /// Sets the microsecond component (10⁻⁶ seconds).
155    ///
156    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
157    /// In debug builds, values >= 1000 trigger an assertion.
158    pub const fn set_microseconds(mut self, microseconds: u32) -> Self {
159        debug_assert!(microseconds < 1000);
160        self.0[1] = microseconds % 1000;
161        self
162    }
163
164    /// Sets the nanosecond component (10⁻⁹ seconds).
165    ///
166    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
167    /// In debug builds, values >= 1000 trigger an assertion.
168    pub const fn set_nanoseconds(mut self, nanoseconds: u32) -> Self {
169        debug_assert!(nanoseconds < 1000);
170        self.0[2] = nanoseconds % 1000;
171        self
172    }
173
174    /// Sets the picosecond component (10⁻¹² seconds).
175    ///
176    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
177    /// In debug builds, values >= 1000 trigger an assertion.
178    pub const fn set_picoseconds(mut self, picoseconds: u32) -> Self {
179        debug_assert!(picoseconds < 1000);
180        self.0[3] = picoseconds % 1000;
181        self
182    }
183
184    /// Sets the femtosecond component (10⁻¹⁵ seconds).
185    ///
186    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
187    /// In debug builds, values >= 1000 trigger an assertion.
188    pub const fn set_femtoseconds(mut self, femtoseconds: u32) -> Self {
189        debug_assert!(femtoseconds < 1000);
190        self.0[4] = femtoseconds % 1000;
191        self
192    }
193
194    /// Sets the attosecond component (10⁻¹⁸ seconds).
195    ///
196    /// Values are automatically normalized to [0, 999] using modulo arithmetic.
197    /// In debug builds, values >= 1000 trigger an assertion.
198    pub const fn set_attoseconds(mut self, attoseconds: u32) -> Self {
199        debug_assert!(attoseconds < 1000);
200        self.0[5] = attoseconds % 1000;
201        self
202    }
203
204    /// Converts the subsecond value to total attoseconds.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use lox_core::time::subsecond::Subsecond;
210    ///
211    /// let s = Subsecond::new().set_milliseconds(123).set_microseconds(456);
212    /// assert_eq!(s.as_attoseconds(), 123456000000000000);
213    /// ```
214    pub const fn as_attoseconds(&self) -> i64 {
215        self.0[0] as i64 * FACTORS[0]
216            + self.0[1] as i64 * FACTORS[1]
217            + self.0[2] as i64 * FACTORS[2]
218            + self.0[3] as i64 * FACTORS[3]
219            + self.0[4] as i64 * FACTORS[4]
220            + self.0[5] as i64 * FACTORS[5]
221    }
222
223    /// Converts the subsecond value to seconds as an `f64`.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use lox_core::time::subsecond::Subsecond;
229    ///
230    /// let s = Subsecond::new().set_milliseconds(500);
231    /// assert_eq!(s.as_seconds_f64(), 0.5);
232    /// ```
233    pub const fn as_seconds_f64(&self) -> f64 {
234        self.as_attoseconds() as f64 * SECONDS_PER_ATTOSECOND
235    }
236
237    /// Returns the millisecond component (10⁻³ seconds).
238    ///
239    /// The returned value is always in the range [0, 999].
240    pub const fn milliseconds(&self) -> u32 {
241        self.0[0]
242    }
243
244    /// Returns the microsecond component (10⁻⁶ seconds).
245    ///
246    /// The returned value is always in the range [0, 999].
247    pub const fn microseconds(&self) -> u32 {
248        self.0[1]
249    }
250
251    /// Returns the nanosecond component (10⁻⁹ seconds).
252    ///
253    /// The returned value is always in the range [0, 999].
254    pub const fn nanoseconds(&self) -> u32 {
255        self.0[2]
256    }
257
258    /// Returns the picosecond component (10⁻¹² seconds).
259    ///
260    /// The returned value is always in the range [0, 999].
261    pub const fn picoseconds(&self) -> u32 {
262        self.0[3]
263    }
264
265    /// Returns the femtosecond component (10⁻¹⁵ seconds).
266    ///
267    /// The returned value is always in the range [0, 999].
268    pub const fn femtoseconds(&self) -> u32 {
269        self.0[4]
270    }
271
272    /// Returns the attosecond component (10⁻¹⁸ seconds).
273    ///
274    /// The returned value is always in the range [0, 999].
275    pub const fn attoseconds(&self) -> u32 {
276        self.0[5]
277    }
278}
279
280impl Ord for Subsecond {
281    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
282        self.as_attoseconds().cmp(&other.as_attoseconds())
283    }
284}
285
286impl PartialOrd for Subsecond {
287    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
288        Some(self.cmp(other))
289    }
290}
291
292impl PartialEq for Subsecond {
293    fn eq(&self, other: &Self) -> bool {
294        self.as_attoseconds() == other.as_attoseconds()
295    }
296}
297
298impl Eq for Subsecond {}
299
300const DIGITS: usize = 18;
301
302impl Display for Subsecond {
303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304        "0.".fmt(f)?;
305        let mut s = self.as_attoseconds().to_string();
306        if s.len() < DIGITS {
307            s = format!("{:0>width$}", s, width = DIGITS);
308        }
309        let p = f.precision().unwrap_or(3).clamp(0, DIGITS);
310        s[0..p].fmt(f)
311    }
312}
313
314/// Error returned when parsing a `Subsecond` from a string fails.
315///
316/// This error occurs when the input string contains non-numeric characters
317/// or exceeds the maximum length of 18 digits.
318#[derive(Debug, Error)]
319#[error("could not parse subsecond from {0}")]
320pub struct SubsecondParseError(String);
321
322impl FromStr for Subsecond {
323    type Err = SubsecondParseError;
324
325    fn from_str(s: &str) -> Result<Self, Self::Err> {
326        let mut this = Self::default();
327
328        if s.is_empty() {
329            return Ok(this);
330        }
331
332        if s.chars().any(|c| !c.is_numeric()) {
333            return Err(SubsecondParseError(s.to_owned()));
334        }
335        let n = s.len();
336        if n > DIGITS {
337            return Err(SubsecondParseError(s.to_owned()));
338        }
339
340        let rem = n % 3;
341        let s = if rem != 0 {
342            let width = n + 3 - rem;
343            format!("{:0<width$}", s)
344        } else {
345            s.to_owned()
346        };
347
348        for i in (0..s.len()).step_by(3) {
349            this.0[i / 3] = s[i..i + 3].parse().unwrap();
350        }
351
352        Ok(this)
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn test_subsecond() {
362        let s = Subsecond::new()
363            .set_milliseconds(123)
364            .set_microseconds(456)
365            .set_nanoseconds(789)
366            .set_picoseconds(123)
367            .set_femtoseconds(456)
368            .set_attoseconds(789);
369
370        assert_eq!(s.as_attoseconds(), 123456789123456789);
371        assert_eq!(s.as_seconds_f64(), 0.1234567891234568);
372        assert_eq!(s.milliseconds(), 123);
373        assert_eq!(s.microseconds(), 456);
374        assert_eq!(s.nanoseconds(), 789);
375        assert_eq!(s.picoseconds(), 123);
376        assert_eq!(s.femtoseconds(), 456);
377        assert_eq!(s.attoseconds(), 789);
378    }
379
380    #[test]
381    fn test_subsecond_from_attoseconds() {
382        let s = Subsecond::from_attoseconds(123456789123456789);
383
384        assert_eq!(s.as_attoseconds(), 123456789123456789);
385        assert_eq!(s.as_seconds_f64(), 0.1234567891234568);
386        assert_eq!(s.milliseconds(), 123);
387        assert_eq!(s.microseconds(), 456);
388        assert_eq!(s.nanoseconds(), 789);
389        assert_eq!(s.picoseconds(), 123);
390        assert_eq!(s.femtoseconds(), 456);
391        assert_eq!(s.attoseconds(), 789);
392    }
393
394    #[test]
395    fn test_subsecond_display() {
396        let s = Subsecond::new()
397            .set_milliseconds(123)
398            .set_microseconds(456)
399            .set_nanoseconds(789)
400            .set_picoseconds(123)
401            .set_femtoseconds(456)
402            .set_attoseconds(789);
403
404        assert_eq!(format!("{}", s), "0.123");
405        assert_eq!(format!("{:.6}", s), "0.123456");
406        assert_eq!(format!("{:.18}", s), "0.123456789123456789");
407    }
408
409    #[test]
410    fn test_subsecond_parse() {
411        let exp = Subsecond::new().set_milliseconds(123).set_microseconds(400);
412        let act: Subsecond = "1234".parse().unwrap();
413        assert_eq!(act, exp);
414
415        let exp = Subsecond::new()
416            .set_milliseconds(123)
417            .set_microseconds(456)
418            .set_nanoseconds(789)
419            .set_picoseconds(123)
420            .set_femtoseconds(456)
421            .set_attoseconds(789);
422        let act: Subsecond = "123456789123456789".parse().unwrap();
423        assert_eq!(act, exp);
424    }
425
426    #[test]
427    #[should_panic]
428    fn test_subsecond_parse_error() {
429        "123foo".parse::<Subsecond>().unwrap();
430    }
431
432    #[test]
433    fn test_subsecond_from_attoseconds_negative() {
434        // -1 attosecond should wrap to 999999999999999999
435        let s = Subsecond::from_attoseconds(-1);
436        assert_eq!(s.as_attoseconds(), 999999999999999999);
437        assert_eq!(s.milliseconds(), 999);
438        assert_eq!(s.microseconds(), 999);
439        assert_eq!(s.nanoseconds(), 999);
440        assert_eq!(s.picoseconds(), 999);
441        assert_eq!(s.femtoseconds(), 999);
442        assert_eq!(s.attoseconds(), 999);
443    }
444
445    #[test]
446    fn test_subsecond_from_attoseconds_zero() {
447        let s = Subsecond::from_attoseconds(0);
448        assert_eq!(s.as_attoseconds(), 0);
449        assert_eq!(s, Subsecond::ZERO);
450    }
451
452    #[test]
453    fn test_subsecond_from_attoseconds_max() {
454        // Maximum subsecond value: 999999999999999999
455        let max = ATTOSECONDS_IN_SECOND - 1;
456        let s = Subsecond::from_attoseconds(max);
457        assert_eq!(s.as_attoseconds(), max);
458        assert_eq!(s.milliseconds(), 999);
459        assert_eq!(s.microseconds(), 999);
460        assert_eq!(s.nanoseconds(), 999);
461        assert_eq!(s.picoseconds(), 999);
462        assert_eq!(s.femtoseconds(), 999);
463        assert_eq!(s.attoseconds(), 999);
464    }
465
466    #[test]
467    fn test_subsecond_from_attoseconds_overflow() {
468        // Values >= 1 second should wrap around
469        let s = Subsecond::from_attoseconds(ATTOSECONDS_IN_SECOND);
470        assert_eq!(s.as_attoseconds(), 0);
471
472        let s = Subsecond::from_attoseconds(ATTOSECONDS_IN_SECOND + 123);
473        assert_eq!(s.as_attoseconds(), 123);
474    }
475
476    #[test]
477    fn test_subsecond_set_methods_max_value() {
478        // Test that 999 is preserved correctly
479        let s = Subsecond::new().set_milliseconds(999);
480        assert_eq!(s.milliseconds(), 999);
481
482        let s = Subsecond::new().set_microseconds(999);
483        assert_eq!(s.microseconds(), 999);
484
485        let s = Subsecond::new().set_nanoseconds(999);
486        assert_eq!(s.nanoseconds(), 999);
487    }
488
489    #[test]
490    fn test_subsecond_display_edge_cases() {
491        // Test zero
492        assert_eq!(format!("{}", Subsecond::ZERO), "0.000");
493
494        // Test precision of 0
495        let s = Subsecond::new().set_milliseconds(123);
496        assert_eq!(format!("{:.0}", s), "");
497
498        // Test precision larger than value
499        assert_eq!(format!("{:.25}", s), "0.123000000000000000");
500    }
501
502    #[test]
503    fn test_subsecond_parse_edge_cases() {
504        // Empty string
505        let s: Subsecond = "".parse().unwrap();
506        assert_eq!(s, Subsecond::ZERO);
507
508        // Single digit
509        let s: Subsecond = "1".parse().unwrap();
510        assert_eq!(s.milliseconds(), 100);
511
512        // Two digits
513        let s: Subsecond = "12".parse().unwrap();
514        assert_eq!(s.milliseconds(), 120);
515
516        // Maximum length (18 digits)
517        let s: Subsecond = "999999999999999999".parse().unwrap();
518        assert_eq!(s.as_attoseconds(), 999999999999999999);
519    }
520
521    #[test]
522    fn test_subsecond_parse_too_long() {
523        // 19 digits should fail
524        let result = "1234567890123456789".parse::<Subsecond>();
525        assert!(result.is_err());
526    }
527
528    #[test]
529    fn test_subsecond_ordering() {
530        let a = Subsecond::from_attoseconds(100);
531        let b = Subsecond::from_attoseconds(200);
532        let c = Subsecond::from_attoseconds(200);
533
534        assert!(a < b);
535        assert!(b > a);
536        assert_eq!(b, c);
537        assert!(b <= c);
538        assert!(b >= c);
539    }
540
541    #[test]
542    fn test_subsecond_equality() {
543        let a = Subsecond::new().set_milliseconds(123);
544        let b = Subsecond::from_attoseconds(123000000000000000);
545
546        assert_eq!(a, b);
547        assert_eq!(a.as_attoseconds(), b.as_attoseconds());
548    }
549}