deep_time/dt/helpers.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_SECF, ATTOS_PER_US_I128, Dt, Real,
4};
5
6impl Dt {
7 /// Clamps an `i128` to the representable range of `i64`.
8 pub const fn to_i64(x: i128) -> i64 {
9 let y = x as i64;
10 if x == y as i128 {
11 y
12 } else if x > 0 {
13 i64::MAX
14 } else {
15 i64::MIN
16 }
17 }
18
19 /// Clamps an `i128` to the representable range of `u64`.
20 pub const fn to_u64(x: i128) -> u64 {
21 if x > u64::MAX as i128 {
22 u64::MAX
23 } else if x < u64::MIN as i128 {
24 u64::MIN
25 } else {
26 x as u64
27 }
28 }
29
30 /// Converts a `u128` to `i128`, saturating at [`i128::MAX`] when the value would wrap.
31 ///
32 /// A bare `x as i128` is wrapping: values above `i128::MAX` become negative
33 /// (e.g. `u128::MAX as i128 == -1`). Prefer this helper whenever a non-negative
34 /// attosecond (or other) magnitude stored as `u128` is fed into signed `i128` arithmetic.
35 pub const fn to_i128(x: u128) -> i128 {
36 if x > i128::MAX as u128 {
37 i128::MAX
38 } else {
39 x as i128
40 }
41 }
42
43 /// Combines a whole unit count and fractional attoseconds within that unit into total
44 /// attoseconds.
45 ///
46 /// Computes `whole * unit_attos + frac_attos`. The fractional part is always **added**, even
47 /// when `whole` is negative — the Euclidean / floor split used by
48 /// [`to_ms_floor`](../struct.Dt.html#method.to_ms_floor),
49 /// [`to_ns_floor`](../struct.Dt.html#method.to_ns_floor).
50 ///
51 /// This is **not** the same as pairing with truncating extractors like
52 /// [`to_ms`](../struct.Dt.html#method.to_ms) (signed remainder).
53 ///
54 /// The fraction is never subtracted even when `whole` is negative.
55 ///
56 /// For the truncating / signed-remainder split, use
57 /// [`unit_and_signed_attos_to_attos`](../struct.Dt.html#method.unit_and_signed_attos_to_attos).
58 #[inline(always)]
59 pub const fn unit_and_attos_to_attos(whole: i128, frac_attos: u128, unit_attos: i128) -> i128 {
60 whole
61 .saturating_mul(unit_attos)
62 .saturating_add(Self::to_i128(frac_attos))
63 }
64
65 /// Combines a whole unit count and a signed fractional remainder into total attoseconds.
66 ///
67 /// The two parts are the left and right sides of a decimal: e.g. `-1.3` units is
68 /// `whole = -1` and a negative `frac_attos` for the `0.3` **(expressed in attoseconds, not
69 /// in the unit itself)**.
70 ///
71 /// Used by constructors such as
72 /// [`from_ms`](../struct.Dt.html#method.from_ms).
73 ///
74 /// For the floor form (fraction always non-negative and always added), use
75 /// [`unit_and_attos_to_attos`](../struct.Dt.html#method.unit_and_attos_to_attos).
76 #[inline(always)]
77 pub const fn unit_and_signed_attos_to_attos(
78 whole: i128,
79 frac_attos: i128,
80 unit_attos: i128,
81 ) -> i128 {
82 whole.saturating_mul(unit_attos).saturating_add(frac_attos)
83 }
84
85 /// Converts whole femtoseconds → total attoseconds (`× 10³`).
86 #[inline(always)]
87 pub const fn fs_to_attos(fs: i128) -> i128 {
88 fs.saturating_mul(ATTOS_PER_FS_I128)
89 }
90
91 /// Converts whole picoseconds → total attoseconds (`× 10⁶`).
92 #[inline(always)]
93 pub const fn ps_to_attos(ps: i128) -> i128 {
94 ps.saturating_mul(ATTOS_PER_PS_I128)
95 }
96
97 /// Converts whole nanoseconds → total attoseconds (`× 10⁹`).
98 #[inline(always)]
99 pub const fn ns_to_attos(ns: i128) -> i128 {
100 ns.saturating_mul(ATTOS_PER_NS_I128)
101 }
102
103 /// Converts whole microseconds → total attoseconds (`× 10¹²`).
104 #[inline(always)]
105 pub const fn us_to_attos(us: i128) -> i128 {
106 us.saturating_mul(ATTOS_PER_US_I128)
107 }
108
109 /// Converts whole milliseconds → total attoseconds (`× 10¹⁵`).
110 #[inline(always)]
111 pub const fn ms_to_attos(ms: i128) -> i128 {
112 ms.saturating_mul(ATTOS_PER_MS_I128)
113 }
114
115 /// Converts whole seconds → total attoseconds (`× 10¹⁸`).
116 #[inline(always)]
117 pub const fn sec_to_attos(sec: i128) -> i128 {
118 sec.saturating_mul(ATTOS_PER_SEC_I128)
119 }
120
121 /// Converts whole minutes → total attoseconds (`× 60 × 10¹⁸`).
122 #[inline(always)]
123 pub const fn mins_to_attos(mins: i128) -> i128 {
124 mins.saturating_mul(ATTOS_PER_MIN)
125 }
126
127 /// Converts whole hours → total attoseconds (`× 3600 × 10¹⁸`).
128 #[inline(always)]
129 pub const fn hours_to_attos(hours: i128) -> i128 {
130 hours.saturating_mul(ATTOS_PER_HOUR)
131 }
132
133 /// Converts total attoseconds → whole seconds as i64
134 #[inline(always)]
135 pub const fn attos_to_sec_i64(attos: i128) -> i64 {
136 Self::to_i64(attos / ATTOS_PER_SEC_I128)
137 }
138
139 /// Clamps `value` to the range `[min, max]`.
140 ///
141 /// This is a `const fn`, so it can be used in const contexts
142 /// (e.g. const generics, statics, const evaluation, etc.).
143 ///
144 /// If `min > max`, the result is equivalent to clamping to `[max, min]`.
145 pub const fn clamp_u8(value: u8, min: u8, max: u8) -> u8 {
146 if value < min {
147 min
148 } else if value > max {
149 max
150 } else {
151 value
152 }
153 }
154
155 /// Clamps `value` to the range `[min, max]`.
156 ///
157 /// This is a `const fn`, so it can be used in const contexts
158 /// (e.g. const generics, statics, const evaluation, etc.).
159 ///
160 /// If `min > max`, the result is equivalent to clamping to `[max, min]`.
161 pub const fn clamp_u64(value: u64, min: u64, max: u64) -> u64 {
162 if value < min {
163 min
164 } else if value > max {
165 max
166 } else {
167 value
168 }
169 }
170
171 /// **Lossy** conversion of u128 attoseconds to → float seconds (s).
172 #[inline(always)]
173 pub const fn attos_to_sec_f(attos: u128) -> Real {
174 f!(attos) / ATTOS_PER_SECF
175 }
176}