Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d5834e9

Browse files
committedMar 11, 2025
Added functionality for int_format_into
1 parent 2c6a12e commit d5834e9

File tree

2 files changed

+280
-0
lines changed

2 files changed

+280
-0
lines changed
 

‎library/core/src/num/int_format.rs

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
use crate::mem::MaybeUninit;
2+
3+
/// A minimal buffer implementation containing elements of type
4+
/// `MaybeUninit::<u8>`.
5+
#[unstable(feature = "int_format_into", issue = "138215")]
6+
#[derive(Debug)]
7+
pub struct NumBuffer<const BUF_SIZE: usize> {
8+
/// A statically allocated array of elements of type
9+
/// `MaybeUninit::<u8>`.
10+
///
11+
/// An alternative to `contents.len()` is `BUF_SIZE`.
12+
pub contents: [MaybeUninit::<u8>; BUF_SIZE]
13+
}
14+
15+
#[unstable(feature = "int_format_into", issue = "138215")]
16+
impl<const BUF_SIZE: usize> NumBuffer<BUF_SIZE> {
17+
18+
#[unstable(feature = "int_format_into", issue = "138215")]
19+
pub fn new() -> Self {
20+
NumBuffer {
21+
contents: [MaybeUninit::<u8>::uninit(); BUF_SIZE]
22+
}
23+
}
24+
}
25+
26+
macro_rules! int_impl_format_into {
27+
($($T:ident)*) => {
28+
$(
29+
#[unstable(feature = "int_format_into", issue = "138215")]
30+
impl $T {
31+
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of
32+
/// type [`NumBuffer`] that is passed by the caller by mutable reference.
33+
///
34+
/// This function panics if `buf` does not have enough size to store
35+
/// the signed decimal version of the number.
36+
///
37+
/// # Examples
38+
/// ```
39+
/// #![feature(int_format_into)]
40+
#[doc = concat!("let n = -32", stringify!($T), ";")]
41+
/// let mut buf = NumBuffer::<3>::new();
42+
///
43+
/// assert_eq!(n.format_into(&mut buf), "-32");
44+
/// ```
45+
///
46+
pub fn format_into<const BUF_SIZE: usize>(self, buf: &mut crate::num::NumBuffer<BUF_SIZE>) -> &str {
47+
// 2 digit decimal look up table
48+
const DEC_DIGITS_LUT: &[u8; 200] = b"\
49+
0001020304050607080910111213141516171819\
50+
2021222324252627282930313233343536373839\
51+
4041424344454647484950515253545556575859\
52+
6061626364656667686970717273747576777879\
53+
8081828384858687888990919293949596979899";
54+
55+
const NEGATIVE_SIGN: &[u8; 1] = b"-";
56+
57+
// counting space for negative sign too, if `self` is negative
58+
let sign_offset = if self < 0 {1} else {0};
59+
let decimal_string_size: usize = self.ilog(10) as usize + 1 + sign_offset;
60+
61+
// `buf` must have minimum size to store the decimal string version.
62+
// BUF_SIZE is the size of the buffer.
63+
if BUF_SIZE < decimal_string_size {
64+
panic!("Not enough buffer size to format into!");
65+
}
66+
67+
// Count the number of bytes in `buf` that are not initialized.
68+
let mut offset = BUF_SIZE;
69+
// Consume the least-significant decimals from a working copy.
70+
let mut remain = self;
71+
72+
// Format per four digits from the lookup table.
73+
// Four digits need a 16-bit $unsigned or wider.
74+
while size_of::<Self>() > 1 && remain > 999.try_into().expect("branch is not hit for types that cannot fit 999 (u8)") {
75+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
76+
// and the while condition ensures at least 4 more decimals.
77+
unsafe { core::hint::assert_unchecked(offset >= 4) }
78+
// SAFETY: The offset counts down from its initial value BUF_SIZE
79+
// without underflow due to the previous precondition.
80+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
81+
offset -= 4;
82+
83+
// pull two pairs
84+
let scale: Self = 1_00_00.try_into().expect("branch is not hit for types that cannot fit 1E4 (u8)");
85+
let quad = remain % scale;
86+
remain /= scale;
87+
let pair1 = (quad / 100) as usize;
88+
let pair2 = (quad % 100) as usize;
89+
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
90+
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
91+
buf.contents[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
92+
buf.contents[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
93+
}
94+
95+
// Format per two digits from the lookup table.
96+
if remain > 9 {
97+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
98+
// and the while condition ensures at least 2 more decimals.
99+
unsafe { core::hint::assert_unchecked(offset >= 2) }
100+
// SAFETY: The offset counts down from its initial value BUF_SIZE
101+
// without underflow due to the previous precondition.
102+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
103+
offset -= 2;
104+
105+
let pair = (remain % 100) as usize;
106+
remain /= 100;
107+
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]);
108+
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]);
109+
}
110+
111+
// Format the last remaining digit, if any.
112+
if remain != 0 || self == 0 {
113+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
114+
// and the if condition ensures (at least) 1 more decimals.
115+
unsafe { core::hint::assert_unchecked(offset >= 1) }
116+
// SAFETY: The offset counts down from its initial value BUF_SIZE
117+
// without underflow due to the previous precondition.
118+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
119+
offset -= 1;
120+
121+
// Either the compiler sees that remain < 10, or it prevents
122+
// a boundary check up next.
123+
let last = (remain & 15) as usize;
124+
buf.contents[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
125+
// not used: remain = 0;
126+
}
127+
128+
if self < 0 {
129+
// SAFETY: All of the decimals (with the sign) fit in buf, since it now is size-checked
130+
// and the if condition ensures (at least) that the sign can be added.
131+
unsafe { core::hint::assert_unchecked(offset >= 1) }
132+
133+
// SAFETY: The offset counts down from its initial value BUF_SIZE
134+
// without underflow due to the previous precondition.
135+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
136+
137+
// Setting sign for the negative number
138+
offset -= 1;
139+
buf.contents[offset].write(NEGATIVE_SIGN[0]);
140+
}
141+
142+
// SAFETY: All buf content since offset is set.
143+
let written = unsafe { buf.contents.get_unchecked(offset..) };
144+
145+
// SAFETY: Writes use ASCII from the lookup table
146+
// (and `NEGATIVE_SIGN` in case of negative numbers) exclusively.
147+
let as_str = unsafe {
148+
str::from_utf8_unchecked(crate::slice::from_raw_parts(
149+
crate::mem::MaybeUninit::slice_as_ptr(written),
150+
written.len(),
151+
))
152+
};
153+
as_str
154+
}
155+
}
156+
)*
157+
};
158+
}
159+
160+
macro_rules! uint_impl_format_into {
161+
($($T:ident)*) => {
162+
$(
163+
#[unstable(feature = "int_format_into", issue = "138215")]
164+
impl $T {
165+
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of
166+
/// type [`NumBuffer`] that is passed by the caller by mutable reference.
167+
///
168+
/// This function panics if `buf` does not have enough size to store
169+
/// the signed decimal version of the number.
170+
///
171+
/// # Examples
172+
/// ```
173+
/// #![feature(int_format_into)]
174+
#[doc = concat!("let n = 32", stringify!($T), ";")]
175+
/// let mut buf = NumBuffer::<3>::new();
176+
///
177+
/// assert_eq!(n.format_into(&mut buf), "32");
178+
/// ```
179+
///
180+
fn format_into<const BUF_SIZE: usize>(self, buf: &mut crate::num::NumBuffer<BUF_SIZE>) -> &str {
181+
// 2 digit decimal look up table
182+
const DEC_DIGITS_LUT: &[u8; 200] = b"\
183+
0001020304050607080910111213141516171819\
184+
2021222324252627282930313233343536373839\
185+
4041424344454647484950515253545556575859\
186+
6061626364656667686970717273747576777879\
187+
8081828384858687888990919293949596979899";
188+
189+
// counting space for negative sign too, if `self` is negative
190+
let decimal_string_size: usize = self.ilog(10) as usize + 1;
191+
192+
// `buf` must have minimum size to store the decimal string version.
193+
// BUF_SIZE is the size of the buffer.
194+
if BUF_SIZE < decimal_string_size {
195+
panic!("Not enough buffer size to format into!");
196+
}
197+
198+
// Count the number of bytes in `buf` that are not initialized.
199+
let mut offset = BUF_SIZE;
200+
// Consume the least-significant decimals from a working copy.
201+
let mut remain = self;
202+
203+
// Format per four digits from the lookup table.
204+
// Four digits need a 16-bit $unsigned or wider.
205+
while size_of::<Self>() > 1 && remain > 999.try_into().expect("branch is not hit for types that cannot fit 999 (u8)") {
206+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
207+
// and the while condition ensures at least 4 more decimals.
208+
unsafe { core::hint::assert_unchecked(offset >= 4) }
209+
// SAFETY: The offset counts down from its initial value BUF_SIZE
210+
// without underflow due to the previous precondition.
211+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
212+
offset -= 4;
213+
214+
// pull two pairs
215+
let scale: Self = 1_00_00.try_into().expect("branch is not hit for types that cannot fit 1E4 (u8)");
216+
let quad = remain % scale;
217+
remain /= scale;
218+
let pair1 = (quad / 100) as usize;
219+
let pair2 = (quad % 100) as usize;
220+
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
221+
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
222+
buf.contents[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
223+
buf.contents[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
224+
}
225+
226+
// Format per two digits from the lookup table.
227+
if remain > 9 {
228+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
229+
// and the while condition ensures at least 2 more decimals.
230+
unsafe { core::hint::assert_unchecked(offset >= 2) }
231+
// SAFETY: The offset counts down from its initial value BUF_SIZE
232+
// without underflow due to the previous precondition.
233+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
234+
offset -= 2;
235+
236+
let pair = (remain % 100) as usize;
237+
remain /= 100;
238+
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]);
239+
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]);
240+
}
241+
242+
// Format the last remaining digit, if any.
243+
if remain != 0 || self == 0 {
244+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
245+
// and the if condition ensures (at least) 1 more decimals.
246+
unsafe { core::hint::assert_unchecked(offset >= 1) }
247+
// SAFETY: The offset counts down from its initial value BUF_SIZE
248+
// without underflow due to the previous precondition.
249+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
250+
offset -= 1;
251+
252+
// Either the compiler sees that remain < 10, or it prevents
253+
// a boundary check up next.
254+
let last = (remain & 15) as usize;
255+
buf.contents[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
256+
// not used: remain = 0;
257+
}
258+
259+
// SAFETY: All buf content since offset is set.
260+
let written = unsafe { buf.contents.get_unchecked(offset..) };
261+
262+
// SAFETY: Writes use ASCII from the lookup table exclusively.
263+
let as_str = unsafe {
264+
str::from_utf8_unchecked(crate::slice::from_raw_parts(
265+
crate::mem::MaybeUninit::slice_as_ptr(written),
266+
written.len(),
267+
))
268+
};
269+
as_str
270+
}
271+
}
272+
)*
273+
};
274+
}
275+
276+
int_impl_format_into! { i8 i16 i32 i64 i128 isize }
277+
uint_impl_format_into! { u8 u16 u32 u64 u128 usize }

‎library/core/src/num/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ mod nonzero;
5050
mod overflow_panic;
5151
mod saturating;
5252
mod wrapping;
53+
mod int_format;
5354

5455
/// 100% perma-unstable
5556
#[doc(hidden)]
@@ -80,6 +81,8 @@ pub use nonzero::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, No
8081
pub use saturating::Saturating;
8182
#[stable(feature = "rust1", since = "1.0.0")]
8283
pub use wrapping::Wrapping;
84+
#[unstable(feature = "int_format_into", issue = "138215")]
85+
pub use int_format::NumBuffer;
8386

8487
macro_rules! u8_xe_bytes_doc {
8588
() => {

0 commit comments

Comments
 (0)
Failed to load comments.