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 4a67fb1

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

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed
 

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

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
pub struct NumBuffer<const BUF_SIZE: usize> {
7+
pub contents: [MaybeUninit::<u8>; BUF_SIZE]
8+
}
9+
10+
impl<const BUF_SIZE: usize> NumBuffer<BUF_SIZE> {
11+
fn new() -> Self {
12+
NumBuffer {
13+
contents: [MaybeUninit::<u8>::uninit(); BUF_SIZE]
14+
}
15+
}
16+
}
17+
18+
macro_rules! int_impl_format_into {
19+
($($T:ident)*) => {
20+
$(
21+
#[unstable(feature = "int_format_into", issue = "138215")]
22+
impl $T {
23+
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of
24+
/// type [`NumBuffer`] that is passed by the caller by mutable reference.
25+
///
26+
/// This function panics if `buf` does not have enough size to store
27+
/// the signed decimal version of the number.
28+
///
29+
/// # Examples
30+
/// ```
31+
/// #![feature(int_format_into)]
32+
#[doc = concat!("let n = 32", stringify!($T), ";")]
33+
/// let mut buf = NumBuffer::<3>::new();
34+
///
35+
/// assert_eq!(n.format_into(&mut buf), "32");
36+
/// ```
37+
///
38+
fn format_into<const BUF_SIZE: usize>(self, buf: &mut crate::num::NumBuffer<BUF_SIZE>) -> &str {
39+
// 2 digit decimal look up table
40+
const DEC_DIGITS_LUT: &[u8; 200] = b"\
41+
0001020304050607080910111213141516171819\
42+
2021222324252627282930313233343536373839\
43+
4041424344454647484950515253545556575859\
44+
6061626364656667686970717273747576777879\
45+
8081828384858687888990919293949596979899";
46+
47+
const NEGATIVE_SIGN: &[u8; 1] = b"-";
48+
49+
// counting space for negative sign too, if `self` is negative
50+
let sign_offset = if self < 0 {1} else {0};
51+
let decimal_string_size: usize = self.ilog(10) as usize + 1 + sign_offset;
52+
53+
// `buf` must have minimum size to store the decimal string version.
54+
// BUF_SIZE is the size of the buffer.
55+
if BUF_SIZE < decimal_string_size {
56+
panic!("Not enough buffer size to format into!");
57+
}
58+
59+
// Count the number of bytes in `buf` that are not initialized.
60+
let mut offset = BUF_SIZE;
61+
// Consume the least-significant decimals from a working copy.
62+
let mut remain = self;
63+
64+
// Format per four digits from the lookup table.
65+
// Four digits need a 16-bit $unsigned or wider.
66+
while size_of::<Self>() > 1 && remain > 999.try_into().expect("branch is not hit for types that cannot fit 999 (u8)") {
67+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
68+
// and the while condition ensures at least 4 more decimals.
69+
unsafe { core::hint::assert_unchecked(offset >= 4) }
70+
// SAFETY: The offset counts down from its initial value BUF_SIZE
71+
// without underflow due to the previous precondition.
72+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
73+
offset -= 4;
74+
75+
// pull two pairs
76+
let scale: Self = 1_00_00.try_into().expect("branch is not hit for types that cannot fit 1E4 (u8)");
77+
let quad = remain % scale;
78+
remain /= scale;
79+
let pair1 = (quad / 100) as usize;
80+
let pair2 = (quad % 100) as usize;
81+
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]);
82+
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]);
83+
buf.contents[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]);
84+
buf.contents[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]);
85+
}
86+
87+
// Format per two digits from the lookup table.
88+
if remain > 9 {
89+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
90+
// and the while condition ensures at least 2 more decimals.
91+
unsafe { core::hint::assert_unchecked(offset >= 2) }
92+
// SAFETY: The offset counts down from its initial value BUF_SIZE
93+
// without underflow due to the previous precondition.
94+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
95+
offset -= 2;
96+
97+
let pair = (remain % 100) as usize;
98+
remain /= 100;
99+
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]);
100+
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]);
101+
}
102+
103+
// Format the last remaining digit, if any.
104+
if remain != 0 || self == 0 {
105+
// SAFETY: All of the decimals fit in buf, since it now is size-checked
106+
// and the if condition ensures (at least) 1 more decimals.
107+
unsafe { core::hint::assert_unchecked(offset >= 1) }
108+
// SAFETY: The offset counts down from its initial value BUF_SIZE
109+
// without underflow due to the previous precondition.
110+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
111+
offset -= 1;
112+
113+
// Either the compiler sees that remain < 10, or it prevents
114+
// a boundary check up next.
115+
let last = (remain & 15) as usize;
116+
buf.contents[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
117+
// not used: remain = 0;
118+
}
119+
120+
if self < 0 {
121+
// SAFETY: All of the decimals (with the sign) fit in buf, since it now is size-checked
122+
// and the if condition ensures (at least) that the sign can be added.
123+
unsafe { core::hint::assert_unchecked(offset >= 1) }
124+
125+
// SAFETY: The offset counts down from its initial value BUF_SIZE
126+
// without underflow due to the previous precondition.
127+
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) }
128+
129+
// Setting sign for the negative number
130+
offset -= 1;
131+
buf.contents[offset].write(NEGATIVE_SIGN[0]);
132+
}
133+
134+
// SAFETY: All buf content since offset is set.
135+
let written = unsafe { buf.contents.get_unchecked(offset..) };
136+
137+
// SAFETY: Writes use ASCII from the lookup table
138+
// (and `NEGATIVE_SIGN` in case of negative numbers) exclusively.
139+
let as_str = unsafe {
140+
str::from_utf8_unchecked(crate::slice::from_raw_parts(
141+
crate::mem::MaybeUninit::slice_as_ptr(written),
142+
written.len(),
143+
))
144+
};
145+
as_str
146+
}
147+
}
148+
)*
149+
};
150+
}
151+
152+
int_impl_format_into! { i8 i16 i32 i64 i128 isize }
153+
int_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.