Skip to content

Commit 2c1eba8

Browse files
Added functionality for int_format_into
1 parent 2c6a12e commit 2c1eba8

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed

library/core/src/num/int_format.rs

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