human_units/
buffer.rs

1#![doc(hidden)]
2
3use core::mem::transmute;
4use core::mem::MaybeUninit;
5use core::str::from_utf8_unchecked;
6use paste::paste;
7
8pub struct Buffer<const N: usize> {
9    data: [MaybeUninit<u8>; N],
10    position: usize,
11}
12
13impl<const N: usize> Buffer<N> {
14    pub const fn new() -> Self {
15        Self {
16            data: [const { MaybeUninit::uninit() }; N],
17            position: 0,
18        }
19    }
20
21    pub fn write_byte(&mut self, ch: u8) {
22        self.data[self.position].write(ch);
23        self.position += 1;
24    }
25
26    pub fn write_str_infallible(&mut self, s: &str) {
27        let bytes = s.as_bytes();
28        let n = bytes.len().min(N - self.position);
29        let src = &mut self.data[self.position..(self.position + n)];
30        let uninit_src: &mut [u8] = unsafe { transmute(src) };
31        uninit_src.copy_from_slice(&bytes[..n]);
32        self.position += n;
33    }
34
35    pub fn as_slice(&self) -> &[u8] {
36        unsafe { transmute(&self.data[..self.position]) }
37    }
38
39    pub unsafe fn as_str(&self) -> &str {
40        from_utf8_unchecked(self.as_slice())
41    }
42}
43
44impl<const N: usize> Default for Buffer<N> {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50macro_rules! parameterize_width {
51    // Linear search.
52    (linear_search, $uint: ident, ($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)) => {
53        paste! {
54            #[inline]
55            const fn [<width_ $uint>](value: $uint) -> u8 {
56                $(
57                    {
58                        const POW: $uint = (10 as $uint).pow($ilog_left);
59                        if value <= POW {
60                            return $ilog_left;
61                        }
62                    }
63                )+
64                $(
65                    {
66                        const POW: $uint = (10 as $uint).pow($ilog_right);
67                        if value <= POW {
68                            return $ilog_right;
69                        }
70                    }
71                )+
72                $uint::MAX.ilog10() as u8 + 1
73            }
74        }
75    };
76    // One step of bisection.
77    (bisection_1, $uint: ident, ($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)) => {
78        paste! {
79            #[inline]
80            const fn [<width_ $uint>](value: $uint) -> u8 {
81                const MIDPOINT: $uint = (10 as $uint).pow($ilog_midpoint).next_power_of_two();
82                if value <= MIDPOINT {
83                    $(
84                        {
85                            const POW: $uint = (10 as $uint).pow($ilog_left);
86                            if value < POW {
87                                return $ilog_left;
88                            }
89                        }
90                    )+
91                    $ilog_midpoint as u8 + 1
92                } else {
93                    $(
94                        {
95                            const POW: $uint = (10 as $uint).pow($ilog_right);
96                            if value < POW {
97                                return $ilog_right;
98                            }
99                        }
100                    )+
101                    $uint::MAX.ilog10() as u8 + 1
102                }
103            }
104        }
105    };
106    // Simple ilog10.
107    (ilog10, $uint: ident, ($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)) => {
108        paste! {
109            #[inline]
110            const fn [<width_ $uint>](value: $uint) -> u8 {
111                value.ilog10() as u8 + 1
112            }
113        }
114    };
115}
116
117macro_rules! parameterize {
118    ($(
119        $uint: ident,
120        $algo: ident,
121        (($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)),
122    )+) => {
123        paste! {
124            $(
125                impl<const N: usize> Buffer<N> {
126                    pub fn [<write_ $uint>](&mut self, mut n: $uint) {
127                        if n == 0 {
128                            self.write_byte(b'0');
129                            return;
130                        }
131                        let width = [<width_ $uint>](n);
132                        let old_position = self.position;
133                        self.position += width as usize;
134                        for i in (old_position..self.position).rev() {
135                            let digit = n % 10;
136                            n /= 10;
137                            self.data[i].write(b'0' + digit as u8);
138                        }
139                    }
140                }
141
142                parameterize_width! {
143                    $algo,
144                    $uint,
145                    ($($ilog_left,)+),
146                    $ilog_midpoint,
147                    ($($ilog_right,)+)
148                }
149            )+
150
151            #[cfg(test)]
152            mod buffer_tests {
153                use super::*;
154
155                use arbtest::arbtest;
156
157                $(
158                    #[cfg(feature = "std")]
159                    #[test]
160                    fn [<test_write_ $uint>]() {
161                        arbtest(|u| {
162                            let number: $uint = u.arbitrary()?;
163                            let expected = format!("{}", number);
164                            let mut buf = Buffer::<64>::new();
165                            buf.[<write_ $uint>](number);
166                            let actual = unsafe { buf.as_str() };
167                            assert_eq!(expected, actual, "number = {number}");
168                            Ok(())
169                        });
170                    }
171
172                    #[cfg(feature = "std")]
173                    #[test]
174                    fn [<test_write_ $uint _power_of_1024>]() {
175                        arbtest(|u| {
176                            let number: $uint = u.int_in_range(0 as $uint..=$uint::MAX/1024)? * 1024;
177                            let expected = format!("{}", number);
178                            let mut buf = Buffer::<64>::new();
179                            buf.[<write_ $uint>](number);
180                            let actual = unsafe { buf.as_str() };
181                            assert_eq!(expected, actual, "number = {number}");
182                            Ok(())
183                        });
184                    }
185                )+
186            }
187        }
188    };
189}
190
191parameterize! {
192    u128, bisection_1,
193    ((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,),
194    20,
195    (20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,)),
196    u64, bisection_1,
197    ((1, 2, 3, 4, 5, 6, 7, 8, 9,), 10, (10, 11, 12, 13, 14, 15, 16, 17, 18, 19,)),
198    u32, ilog10, ((1, 2, 3, 4,), 5, (5, 6, 7, 8, 9,)),
199    u16, bisection_1, ((1, 2,), 2, (3, 4,)),
200}
201
202impl<const N: usize> core::fmt::Write for Buffer<N> {
203    fn write_str(&mut self, s: &str) -> core::fmt::Result {
204        self.write_str_infallible(s);
205        Ok(())
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_max_power_of_10() {
215        macro_rules! check {
216            ($uint: ident, $ilog: expr, $ilog_midpoint: expr) => {{
217                paste! {
218                    const ILOG: u32 = $uint::MAX.ilog(10);
219                    assert_eq!(ILOG, $ilog);
220                    const MAX_POWER_OF_10: $uint = (10 as $uint).pow(ILOG);
221                    assert_eq!(None, MAX_POWER_OF_10.checked_mul(10));
222                    const MIDPOINT: $uint = (10 as $uint).pow($ilog_midpoint);
223                    assert_eq!($ilog_midpoint, MIDPOINT.next_power_of_two().ilog10());
224                }
225            }};
226        }
227        check!(u16, 4, 3);
228        check!(u32, 9, 5);
229        check!(u64, 19, 10);
230        check!(u128, 38, 20);
231    }
232}