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