1pub const U32_DEC_MAX: usize = 10;
17pub const I32_DEC_MAX: usize = 11;
19pub const U64_DEC_MAX: usize = 20;
21
22#[inline(always)]
27fn ascii(bytes: &[u8]) -> &str {
28 debug_assert!(bytes.is_ascii());
29 unsafe { core::str::from_utf8_unchecked(bytes) }
31}
32
33pub fn u32_dec(buf: &mut [u8], mut v: u32) -> &str {
39 let mut tmp = [0u8; U32_DEC_MAX];
40 let mut i = tmp.len();
41 loop {
42 let q = v / 10;
43 i -= 1;
44 tmp[i] = b'0' + (v - q * 10) as u8;
45 v = q;
46 if v == 0 {
47 break;
48 }
49 }
50 let n = tmp.len() - i;
51 buf[..n].copy_from_slice(&tmp[i..]);
52 ascii(&buf[..n])
53}
54
55pub fn i32_dec(buf: &mut [u8], v: i32) -> &str {
60 if v >= 0 {
61 return u32_dec(buf, v as u32);
62 }
63 buf[0] = b'-';
66 let n = 1 + u32_dec(&mut buf[1..], v.unsigned_abs()).len();
67 ascii(&buf[..n])
68}
69
70pub fn u64_dec(buf: &mut [u8], mut v: u64) -> &str {
75 let mut tmp = [0u8; U64_DEC_MAX];
76 let mut i = tmp.len();
77 loop {
78 i -= 1;
79 tmp[i] = b'0' + (v % 10) as u8;
80 v /= 10;
81 if v == 0 {
82 break;
83 }
84 }
85 let n = tmp.len() - i;
86 buf[..n].copy_from_slice(&tmp[i..]);
87 ascii(&buf[..n])
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn u32_basics() {
96 let mut b = [0u8; U32_DEC_MAX];
97 assert_eq!(u32_dec(&mut b, 0), "0");
98 assert_eq!(u32_dec(&mut b, 7), "7");
99 assert_eq!(u32_dec(&mut b, 240), "240");
100 assert_eq!(u32_dec(&mut b, u32::MAX), "4294967295");
101 }
102
103 #[test]
104 fn i32_signs_and_extremes() {
105 let mut b = [0u8; I32_DEC_MAX];
106 assert_eq!(i32_dec(&mut b, 0), "0");
107 assert_eq!(i32_dec(&mut b, -1), "-1");
108 assert_eq!(i32_dec(&mut b, 12345), "12345");
109 assert_eq!(i32_dec(&mut b, i32::MIN), "-2147483648");
110 assert_eq!(i32_dec(&mut b, i32::MAX), "2147483647");
111 }
112
113 #[test]
116 #[cfg_attr(
117 miri,
118 ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
119 )]
120 fn u32_matches_u64_formatter() {
121 let check = |v: u32| {
122 let mut a = [0u8; U32_DEC_MAX];
123 let mut b = [0u8; U64_DEC_MAX];
124 assert_eq!(u32_dec(&mut a, v), u64_dec(&mut b, u64::from(v)), "{v}");
125 };
126 let mut power = 1u32;
127 loop {
128 for v in [
129 power - 1,
130 power,
131 power + 1,
132 power.wrapping_mul(2) - 1,
133 power.wrapping_mul(9),
134 ] {
135 check(v);
136 }
137 match power.checked_mul(10) {
138 Some(next) => power = next,
139 None => break,
140 }
141 }
142 for v in (0..100_000).chain(u32::MAX - 100_000..=u32::MAX) {
143 check(v);
144 }
145 let mut seed = 0x1234_5678u32;
146 for _ in 0..1_000_000 {
147 seed ^= seed << 13;
148 seed ^= seed >> 17;
149 seed ^= seed << 5;
150 check(seed);
151 check(seed >> (seed % 32));
152 }
153 }
154
155 #[test]
158 #[ignore]
159 fn u32_matches_u64_formatter_exhaustively() {
160 let mut a = [0u8; U32_DEC_MAX];
161 let mut b = [0u8; U64_DEC_MAX];
162 for v in 0..=u32::MAX {
163 assert_eq!(u32_dec(&mut a, v), u64_dec(&mut b, u64::from(v)), "{v}");
164 }
165 }
166
167 #[test]
168 #[cfg_attr(
169 miri,
170 ignore = "exhaustive sweep, minutes under Miri; make test runs it natively"
171 )]
172 fn i32_matches_u64_formatter() {
173 let mut seed = 0x9e37_79b9u32;
174 for _ in 0..200_000 {
175 seed ^= seed << 13;
176 seed ^= seed >> 17;
177 seed ^= seed << 5;
178 let v = seed as i32 >> (seed % 32);
179 let mut a = [0u8; I32_DEC_MAX];
180 let mut b = [0u8; U64_DEC_MAX];
181 let magnitude = u64_dec(&mut b, u64::from(v.unsigned_abs()));
182 let sign = if v < 0 { "-" } else { "" };
183 assert_eq!(i32_dec(&mut a, v), [sign, magnitude].concat(), "{v}");
184 }
185 }
186
187 #[test]
188 fn u64_extremes() {
189 let mut b = [0u8; U64_DEC_MAX];
190 assert_eq!(u64_dec(&mut b, u64::MAX), "18446744073709551615");
191 assert_eq!(u64_dec(&mut b, 10_000), "10000");
192 }
193}