Skip to main content

time/
num_fmt.rs

1//! Formatting utilities for numbers.
2//!
3//! These functions are low-level, but are designed to be _extremely_ fast for their designed use
4//! cases. They have strict requirements, and may not return the most ergonomic types to avoid
5//! unnecessary allocations and copying.
6
7use core::mem::MaybeUninit;
8use core::ops::Deref;
9use core::slice;
10
11use deranged::{ru8, ru16, ru32};
12
13static SINGLE_DIGITS: [u8; 10] = *b"0123456789";
14
15static ZERO_PADDED_PAIRS: [u8; 200] = *b"0001020304050607080910111213141516171819\
16                                         2021222324252627282930313233343536373839\
17                                         4041424344454647484950515253545556575859\
18                                         6061626364656667686970717273747576777879\
19                                         8081828384858687888990919293949596979899";
20
21#[cfg(feature = "formatting")]
22static SPACE_PADDED_PAIRS: [u8; 200] = *b" 0 1 2 3 4 5 6 7 8 910111213141516171819\
23                                          2021222324252627282930313233343536373839\
24                                          4041424344454647484950515253545556575859\
25                                          6061626364656667686970717273747576777879\
26                                          8081828384858687888990919293949596979899";
27
28/// A string type with a maximum length known at compile time, stored on the stack.
29///
30/// Note that while the _maximum_ length is known at compile time, the string may be shorter. This
31/// information is stored inline.
32#[derive(Clone, Copy)]
33pub(crate) struct StackStr<const MAX_LEN: usize> {
34    buf: [MaybeUninit<u8>; MAX_LEN],
35    len: usize,
36}
37
38impl<const MAX_LEN: usize> StackStr<MAX_LEN> {
39    /// # Safety:
40    ///
41    /// - `buf` must be initialized for at least `len` bytes.
42    /// - The first `len` bytes of `buf` must be valid UTF-8.
43    #[inline]
44    pub(crate) const unsafe fn new(buf: [MaybeUninit<u8>; MAX_LEN], len: usize) -> Self {
45        debug_assert!(len <= MAX_LEN);
46        Self { buf, len }
47    }
48}
49
50impl<const MAX_LEN: usize> Deref for StackStr<MAX_LEN> {
51    type Target = str;
52
53    #[inline]
54    fn deref(&self) -> &Self::Target {
55        // Safety: This type can only be constructed when the caller asserts that the buffer is
56        // valid UTF-8 for the first `len` bytes.
57        unsafe { str_from_raw_parts(self.buf.as_ptr().cast(), self.len) }
58    }
59}
60
61/// # Safety
62///
63/// - `ptr` must be non-null and point to `len` initialized bytes of UTF-8 data.
64/// - `ptr` is valid for (and not mutated during) lifetime `'a`.
65#[inline]
66pub(crate) const unsafe fn str_from_raw_parts<'a>(ptr: *const u8, len: usize) -> &'a str {
67    // Safety: The caller must ensure that `ptr` is valid for `len` bytes and that the bytes are
68    // valid UTF-8. The caller must also ensure that the lifetime `'a` is valid for the returned
69    // string.
70    unsafe { str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)) }
71}
72
73#[inline]
74const fn div_100(n: ru16<0, 9_999>) -> [ru8<0, 99>; 2] {
75    const EXP: u32 = 19; // 19 is faster or equal to 12 even for 3 digits.
76    const SIG: u32 = (1 << EXP) / 100 + 1;
77
78    let n = n.get();
79
80    let high = (n as u32 * SIG) >> EXP; // value / 100
81    let low = n as u32 - high * 100;
82
83    // Safety: `high` is guaranteed to be less than 100 and `low` is guaranteed to be less than 100
84    // due to the arithmetic above.
85    unsafe {
86        [
87            ru8::new_unchecked(high as u8),
88            ru8::new_unchecked(low as u8),
89        ]
90    }
91}
92
93/// Obtain a string containing a single ASCII digit representing `n`.
94#[inline]
95pub(crate) const fn single_digit(n: ru8<0, 9>) -> &'static str {
96    // Safety: We're staying within the bounds of the array. The array contains only ASCII
97    // characters, so it's valid UTF-8.
98    unsafe { str_from_raw_parts(SINGLE_DIGITS.as_ptr().add(n.get() as usize), 1) }
99}
100
101/// Obtain a string of one or two ASCII digits representing `n`. No leading zeros or spaces are
102/// included.
103#[inline]
104pub(crate) const fn one_to_two_digits_no_padding(n: ru8<0, 99>) -> &'static str {
105    let n = n.get();
106    let is_single_digit = n < 10;
107    // Safety: We're staying within the bounds of the array. The array contains only ASCII
108    // characters, so it's valid UTF-8.
109    unsafe {
110        str_from_raw_parts(
111            ZERO_PADDED_PAIRS
112                .as_ptr()
113                .add((n as usize) * 2 + is_single_digit as usize),
114            2 - is_single_digit as usize,
115        )
116    }
117}
118
119/// Obtain a string of two ASCII digits representing `n`. This includes a leading zero if `n` is
120/// less than 10.
121#[inline]
122pub(crate) const fn two_digits_zero_padded(n: ru8<0, 99>) -> &'static str {
123    // Safety: We're staying within the bounds of the array. The array contains only ASCII
124    // characters, so it's valid UTF-8.
125    unsafe { str_from_raw_parts(ZERO_PADDED_PAIRS.as_ptr().add((n.get() as usize) * 2), 2) }
126}
127
128/// Obtain a string of two ASCII digits representing `n`. This includes a leading space if `n` is
129/// less than 10.
130#[inline]
131#[cfg(feature = "formatting")]
132pub(crate) const fn two_digits_space_padded(n: ru8<0, 99>) -> &'static str {
133    // Safety: We're staying within the bounds of the array. The array contains only ASCII
134    // characters, so it's valid UTF-8.
135    unsafe { str_from_raw_parts(SPACE_PADDED_PAIRS.as_ptr().add((n.get() as usize) * 2), 2) }
136}
137
138/// Obtain two strings of ASCII digits representing `n`. The first string is most significant. No
139/// leading zeros or spaces are included.
140#[inline]
141#[cfg(feature = "formatting")]
142pub(crate) fn one_to_three_digits_no_padding(n: ru16<0, 999>) -> [&'static str; 2] {
143    if let Some(n) = n.narrow::<0, 99>() {
144        crate::hint::cold_path();
145        ["", one_to_two_digits_no_padding(n.into())]
146    } else {
147        three_digits_zero_padded(n)
148    }
149}
150
151/// Obtain two strings of ASCII digits representing `n`. The first string is the most significant.
152/// Leading zeros are included if the number has fewer than 3 digits.
153#[inline]
154#[cfg(feature = "formatting")]
155pub(crate) const fn three_digits_zero_padded(n: ru16<0, 999>) -> [&'static str; 2] {
156    let [high, low] = div_100(n.expand());
157    [
158        // Safety: `high` is guaranteed to be less than 10 due to the range of the input.
159        single_digit(unsafe { high.narrow_unchecked() }),
160        two_digits_zero_padded(low),
161    ]
162}
163
164/// Obtain two strings of ASCII digits representing `n`. The first string is the most significant.
165/// Leading spaces are included if the number has fewer than 3 digits.
166#[inline]
167#[cfg(feature = "formatting")]
168pub(crate) const fn three_digits_space_padded(n: ru16<0, 999>) -> [&'static str; 2] {
169    let [high, low] = div_100(n.expand());
170
171    if let Some(high) = high.narrow::<1, 9>() {
172        [single_digit(high.expand()), two_digits_zero_padded(low)]
173    } else {
174        [" ", two_digits_space_padded(low)]
175    }
176}
177
178/// Obtain two strings of ASCII digits representing `n`. The first string is the most significant.
179/// No leading zeros or spaces are included.
180#[inline]
181#[cfg(feature = "formatting")]
182pub(crate) fn one_to_four_digits_no_padding(n: ru16<0, 9_999>) -> [&'static str; 2] {
183    if let Some(n) = n.narrow::<0, 999>() {
184        crate::hint::cold_path();
185        one_to_three_digits_no_padding(n)
186    } else {
187        four_digits_zero_padded(n)
188    }
189}
190
191/// Obtain two strings of two ASCII digits each representing `n`. The first string is the most
192/// significant. Leading zeros are included if the number has fewer than 4 digits.
193#[inline]
194pub(crate) const fn four_digits_zero_padded(n: ru16<0, 9_999>) -> [&'static str; 2] {
195    let [high, low] = div_100(n);
196    [two_digits_zero_padded(high), two_digits_zero_padded(low)]
197}
198
199/// Obtain two strings of two ASCII digits each representing `n`. The first string is the most
200/// significant. Leading spaces are included if the number has fewer than 4 digits.
201#[inline]
202#[cfg(feature = "formatting")]
203pub(crate) const fn four_digits_space_padded(n: ru16<0, 9_999>) -> [&'static str; 2] {
204    let [high, low] = div_100(n);
205
206    if high.get() == 0 {
207        ["  ", two_digits_space_padded(low)]
208    } else {
209        [two_digits_space_padded(high), two_digits_zero_padded(low)]
210    }
211}
212
213/// Obtain three strings which together represent `n`. The first string is the most significant.
214/// Leading zeros are included if the number has fewer than 4 digits. The first string will be empty
215/// if `n` is less than 10,000.
216#[inline]
217pub(crate) const fn four_to_six_digits(n: ru32<0, 999_999>) -> [&'static str; 3] {
218    let n = n.get();
219
220    let (first_two, remaining) = (n / 10_000, n % 10_000);
221
222    let size = 2 - (first_two < 10) as usize - (first_two == 0) as usize;
223    let offset = first_two as usize * 2 + 2 - size;
224
225    // Safety: `offset` is within the bounds of the array. The array contains only ASCII characters,
226    // so it's valid UTF-8.
227    let first_two = unsafe { str_from_raw_parts(ZERO_PADDED_PAIRS.as_ptr().add(offset), size) };
228    // Safety: `remaining` is guaranteed to be less than 10,000 due to the modulus above.
229    let [second_two, last_two] =
230        four_digits_zero_padded(unsafe { ru16::new_unchecked(remaining as u16) });
231    [first_two, second_two, last_two]
232}
233
234/// Obtain three strings which together represent `n`. The first string is the most significant.
235/// Leading zeros are included if the number has fewer than 5 digits. The first string will be empty
236/// if `n` is less than 10,000.
237#[inline]
238#[cfg(feature = "formatting")]
239pub(crate) const fn five_digits_zero_padded(n: ru32<0, 99_999>) -> [&'static str; 3] {
240    let n = n.get();
241
242    let (first_one, remaining) = (n / 10_000, n % 10_000);
243
244    // Safety: `first_one` is guaranteed to be less than 10 due to the division above.
245    let first_one = single_digit(unsafe { ru8::new_unchecked(first_one as u8) });
246    // Safety: `remaining` is guaranteed to be less than 10,000 due to the modulus above.
247    let [second_two, last_two] =
248        four_digits_zero_padded(unsafe { ru16::new_unchecked(remaining as u16) });
249    [first_one, second_two, last_two]
250}
251
252/// Obtain three strings which together represent `n`. The first string is the most significant.
253/// Leading zeroes are included if the number has fewer than 6 digits.
254#[inline]
255#[cfg(feature = "formatting")]
256pub(crate) const fn six_digits_zero_padded(n: ru32<0, 999_999>) -> [&'static str; 3] {
257    let n = n.get();
258
259    let (first_two, remaining) = (n / 10_000, n % 10_000);
260
261    // Safety: `first_two` is guaranteed to be less than 100 due to the division above.
262    let first_two = two_digits_zero_padded(unsafe { ru8::new_unchecked(first_two as u8) });
263    // Safety: `remaining` is guaranteed to be less than 10,000 due to the modulus above.
264    let [second_two, last_two] =
265        four_digits_zero_padded(unsafe { ru16::new_unchecked(remaining as u16) });
266    [first_two, second_two, last_two]
267}
268
269/// Obtain five strings which together represent `n`, which is a number of nanoseconds.
270///
271/// This value is intended to be used after a decimal point to represent a fractional second. The
272/// first string will always contain exactly one digit; the remaining four will contain two digits
273/// each.
274#[inline]
275pub(crate) const fn subsecond_from_nanos(n: ru32<0, 999_999_999>) -> [&'static str; 5] {
276    let n = n.get();
277    let (digits_1_thru_5, digits_6_thru_9) = (n / 10_000, n % 10_000);
278    let (digit_1, digits_2_thru_5) = (digits_1_thru_5 / 10_000, digits_1_thru_5 % 10_000);
279
280    // Safety: The caller must ensure that `n` is less than 1,000,000,000. Combined with the
281    // arithmetic above, this guarantees that all values are in the required ranges.
282    unsafe {
283        let digit_1 = single_digit(ru8::new_unchecked(digit_1 as u8));
284        let [digits_2_and_3, digits_4_and_5] =
285            four_digits_zero_padded(ru16::new_unchecked(digits_2_thru_5 as u16));
286        let [digits_6_and_7, digits_8_and_9] =
287            four_digits_zero_padded(ru16::new_unchecked(digits_6_thru_9 as u16));
288
289        [
290            digit_1,
291            digits_2_and_3,
292            digits_4_and_5,
293            digits_6_and_7,
294            digits_8_and_9,
295        ]
296    }
297}
298
299/// Obtain a string of 1 to 9 ASCII digits representing `n`, which is a number of nanoseconds.
300///
301/// This value is intended to be used after a decimal point to represent a fractional second.
302/// Trailing zeros are truncated, but at least one digit is always present.
303#[inline]
304pub(crate) const fn truncated_subsecond_from_nanos(n: ru32<0, 999_999_999>) -> StackStr<9> {
305    #[repr(C, align(8))]
306    #[derive(Clone, Copy)]
307    struct Digits {
308        _padding: MaybeUninit<[u8; 7]>,
309        digit_1: u8,
310        digits_2_thru_9: [u8; 8],
311    }
312
313    let [
314        digit_1,
315        digits_2_and_3,
316        digits_4_and_5,
317        digits_6_and_7,
318        digits_8_and_9,
319    ] = subsecond_from_nanos(n);
320
321    // Ensure that digits 2 thru 9 are stored as a single array that is 8-aligned. This allows the
322    // conversion to a `u64` to be zero cost, resulting in a nontrivial performance improvement.
323    let buf = Digits {
324        _padding: MaybeUninit::uninit(),
325        digit_1: digit_1.as_bytes()[0],
326        digits_2_thru_9: [
327            digits_2_and_3.as_bytes()[0],
328            digits_2_and_3.as_bytes()[1],
329            digits_4_and_5.as_bytes()[0],
330            digits_4_and_5.as_bytes()[1],
331            digits_6_and_7.as_bytes()[0],
332            digits_6_and_7.as_bytes()[1],
333            digits_8_and_9.as_bytes()[0],
334            digits_8_and_9.as_bytes()[1],
335        ],
336    };
337
338    // By converting the bytes into a single integer, we can effectively perform an equality check
339    // against b'0' for all bytes at once. This is actually faster than using portable SIMD (even
340    // with `-Ctarget-cpu=native`).
341    let bitmask = u64::from_le_bytes(buf.digits_2_thru_9) ^ u64::from_le_bytes([b'0'; 8]);
342    let digits_to_truncate = bitmask.leading_zeros() / 8;
343    let len = 9 - digits_to_truncate as usize;
344
345    // Safety: All bytes are initialized and valid UTF-8, and `len` represents the number of bytes
346    // we wish to display (that is between 1 and 9 inclusive). `Digits` is `#[repr(C)]`, so the
347    // layout is guaranteed.
348    unsafe {
349        StackStr::new(
350            *(&raw const buf)
351                .byte_add(core::mem::offset_of!(Digits, digit_1))
352                .cast(),
353            len,
354        )
355    }
356}