11//! Integer and floating-point number formatting
22
3+ use crate :: fmt:: NumBuffer ;
34use crate :: mem:: MaybeUninit ;
45use crate :: num:: fmt as numfmt;
56use crate :: ops:: { Div , Rem , Sub } ;
@@ -199,6 +200,47 @@ static DEC_DIGITS_LUT: &[u8; 200] = b"\
199200 6061626364656667686970717273747576777879\
200201 8081828384858687888990919293949596979899";
201202
203+ static NEGATIVE_SIGN : & [ u8 ; 1 ] = b"-" ;
204+
205+ // SAFETY: safety is ensured by the caller about:
206+ // 1. The contents of `buf` containing only ASCII characters
207+ // 2. `offset` being bound checked
208+ unsafe fn _extract_str_from_buf ( buf : & NumBuffer , offset : usize ) -> & str {
209+ // SAFETY: safety is ensured by the caller about:
210+ // 1. `offset` being bound checked
211+ let written = unsafe { buf. contents . get_unchecked ( offset..) } ;
212+
213+ // SAFETY: safety is ensured by the caller about:
214+ // 1. The contents of `buf` containing only ASCII characters
215+ let as_str = unsafe {
216+ str:: from_utf8_unchecked ( slice:: from_raw_parts (
217+ MaybeUninit :: slice_as_ptr ( written) ,
218+ written. len ( ) ,
219+ ) )
220+ } ;
221+
222+ as_str
223+ }
224+
225+ // SAFETY: safety is ensured by the caller about:
226+ // 1. `start_offset` being bound checked
227+ unsafe fn _add_negative_sign (
228+ is_nonnegative : bool ,
229+ buf : & mut NumBuffer ,
230+ start_offset : usize ,
231+ ) -> usize {
232+ if is_nonnegative {
233+ return start_offset;
234+ }
235+
236+ let offset = start_offset - 1 ;
237+
238+ // Setting sign for the negative number
239+ buf. contents [ offset] . write ( NEGATIVE_SIGN [ 0 ] ) ;
240+
241+ offset
242+ }
243+
202244macro_rules! impl_Display {
203245 ( $( $signed: ident, $unsigned: ident, ) * ; as $u: ident via $conv_fn: ident named $gen_name: ident) => {
204246
@@ -212,7 +254,9 @@ macro_rules! impl_Display {
212254 }
213255 #[ cfg( feature = "optimize_for_size" ) ]
214256 {
215- $gen_name( self . $conv_fn( ) , true , f)
257+ let mut buf = NumBuffer :: new( ) ;
258+ let as_str = $gen_name( self . $conv_fn( ) , true , & mut buf) ;
259+ f. pad_integral( true , "" , as_str)
216260 }
217261 }
218262 }
@@ -226,31 +270,122 @@ macro_rules! impl_Display {
226270 }
227271 #[ cfg( feature = "optimize_for_size" ) ]
228272 {
229- return $gen_name( self . unsigned_abs( ) . $conv_fn( ) , * self >= 0 , f) ;
273+ let mut buf = NumBuffer :: new( ) ;
274+
275+ // not setting the sign here, hence sending in `true`
276+ let as_str = $gen_name( self . unsigned_abs( ) . $conv_fn( ) , true , & mut buf) ;
277+ f. pad_integral( * self >= 0 , "" , as_str)
230278 }
231279 }
232280 }
233281
282+ #[ unstable( feature = "int_format_into" , issue = "138215" ) ]
283+ impl $signed {
284+ /// Allows users to write an integer (in signed decimal format) into a variable `buf` of
285+ /// type [`NumBuffer`] that is passed by the caller by mutable reference.
286+ ///
287+ /// # Examples
288+ /// ```
289+ /// #![feature(int_format_into)]
290+ /// use core::fmt::NumBuffer;
291+ ///
292+ #[ doc = concat!( "let n = -32" , stringify!( $signed) , ";" ) ]
293+ /// let mut buf = NumBuffer::new();
294+ /// assert_eq!(n.format_into(&mut buf), "-32");
295+ ///
296+ #[ doc = concat!( "let n2 = " , stringify!( $signed:: MIN ) , ";" ) ]
297+ /// let mut buf2 = NumBuffer::new();
298+ #[ doc = concat!( "assert_eq!(n2.format_into(&mut buf2), " , stringify!( $signed:: MIN ) , ".to_string());" ) ]
299+ ///
300+ #[ doc = concat!( "let n3 = " , stringify!( $signed:: MAX ) , ";" ) ]
301+ /// let mut buf3 = NumBuffer::new();
302+ #[ doc = concat!( "assert_eq!(n3.format_into(&mut buf3), " , stringify!( $signed:: MAX ) , ".to_string());" ) ]
303+ /// ```
304+ ///
305+ pub fn format_into( self , buf: & mut NumBuffer ) -> & str {
306+ #[ cfg( not( feature = "optimize_for_size" ) ) ]
307+ {
308+ let is_nonnegative = self > 0 ;
309+ let offset = self . unsigned_abs( ) . _write_into_buf( buf) ;
310+
311+ // SAFETY: `offset >= 2` (unchanged if `is_nonnegative` is true) and
312+ // `offset >= 1` (incase `is_nonnegative` is false) so
313+ // `buf_ptr[offset..offset + 1]` is safe to access.
314+ unsafe { _add_negative_sign( is_nonnegative, buf, offset) ; }
315+
316+ // SAFETY: All buf content since offset is set, and
317+ // writes use ASCII from the lookup table exclusively.
318+ let as_str = unsafe { _extract_str_from_buf( buf, offset) } ;
319+
320+ as_str
321+ }
322+
323+ #[ cfg( feature = "optimize_for_size" ) ]
324+ {
325+ let is_nonnegative = self > 0 ;
326+ $gen_name( self . unsigned_abs( ) , is_nonnegative, buf)
327+ }
328+
329+ }
330+ }
331+
332+ #[ unstable( feature = "int_format_into" , issue = "138215" ) ]
333+ impl $unsigned {
334+ /// Allows users to write an integer (in signed decimal format) into a variable `buf` of
335+ /// type [`NumBuffer`] that is passed by the caller by mutable reference.
336+ ///
337+ /// # Examples
338+ /// ```
339+ /// #![feature(int_format_into)]
340+ /// use core::fmt::NumBuffer;
341+ ///
342+ #[ doc = concat!( "let n = 32" , stringify!( $unsigned) , ";" ) ]
343+ /// let mut buf = NumBuffer::new();
344+ /// assert_eq!(n.format_into(&mut buf), "32");
345+ ///
346+ #[ doc = concat!( "let n2 = " , stringify!( $unsigned:: MAX ) , ";" ) ]
347+ /// let mut buf2 = NumBuffer::new();
348+ #[ doc = concat!( "assert_eq!(n2.format_into(&mut buf2), " , stringify!( $unsigned:: MAX ) , ".to_string());" ) ]
349+ /// ```
350+ ///
351+ pub fn format_into( self , buf: & mut NumBuffer ) -> & str {
352+ #[ cfg( not( feature = "optimize_for_size" ) ) ]
353+ {
354+ let offset = self . _write_into_buf( buf) ;
355+
356+ // SAFETY: All contents in `buf` since offset is set, and
357+ // writes use ASCII from the lookup table exclusively.
358+ let as_str = unsafe { _extract_str_from_buf( buf, offset) } ;
359+
360+ as_str
361+ }
362+
363+ #[ cfg( feature = "optimize_for_size" ) ]
364+ {
365+ $gen_name( self , true , buf)
366+ }
367+
368+ }
369+ }
370+
234371 #[ cfg( not( feature = "optimize_for_size" ) ) ]
235372 impl $unsigned {
236- fn _fmt( self , is_nonnegative: bool , f: & mut fmt:: Formatter <' _>) -> fmt:: Result {
237- const MAX_DEC_N : usize = $unsigned:: MAX . ilog( 10 ) as usize + 1 ;
238- // Buffer decimals for $unsigned with right alignment.
239- let mut buf = [ MaybeUninit :: <u8 >:: uninit( ) ; MAX_DEC_N ] ;
373+ fn _write_into_buf( self , buf: & mut NumBuffer ) -> usize {
240374 // Count the number of bytes in buf that are not initialized.
241- let mut offset = buf. len( ) ;
375+ let mut offset = buf. contents . len( ) ;
242376 // Consume the least-significant decimals from a working copy.
243377 let mut remain = self ;
244378
245379 // Format per four digits from the lookup table.
246380 // Four digits need a 16-bit $unsigned or wider.
247381 while size_of:: <Self >( ) > 1 && remain > 999 . try_into( ) . expect( "branch is not hit for types that cannot fit 999 (u8)" ) {
248- // SAFETY: All of the decimals fit in buf due to MAX_DEC_N
382+ // SAFETY: All of the decimals fit in `buf` since `buf` is large enough to
383+ // accommodate the largest representation of a number possible (that of i128::MIN)
249384 // and the while condition ensures at least 4 more decimals.
250385 unsafe { core:: hint:: assert_unchecked( offset >= 4 ) }
251- // SAFETY: The offset counts down from its initial buf.len()
386+ // SAFETY: The offset counts down from its initial size
252387 // without underflow due to the previous precondition.
253- unsafe { core:: hint:: assert_unchecked( offset <= buf. len( ) ) }
388+ unsafe { core:: hint:: assert_unchecked( offset <= buf. contents . len( ) ) }
254389 offset -= 4 ;
255390
256391 // pull two pairs
@@ -259,69 +394,74 @@ macro_rules! impl_Display {
259394 remain /= scale;
260395 let pair1 = ( quad / 100 ) as usize ;
261396 let pair2 = ( quad % 100 ) as usize ;
262- buf[ offset + 0 ] . write( DEC_DIGITS_LUT [ pair1 * 2 + 0 ] ) ;
263- buf[ offset + 1 ] . write( DEC_DIGITS_LUT [ pair1 * 2 + 1 ] ) ;
264- buf[ offset + 2 ] . write( DEC_DIGITS_LUT [ pair2 * 2 + 0 ] ) ;
265- buf[ offset + 3 ] . write( DEC_DIGITS_LUT [ pair2 * 2 + 1 ] ) ;
397+ buf. contents [ offset + 0 ] . write( DEC_DIGITS_LUT [ pair1 * 2 + 0 ] ) ;
398+ buf. contents [ offset + 1 ] . write( DEC_DIGITS_LUT [ pair1 * 2 + 1 ] ) ;
399+ buf. contents [ offset + 2 ] . write( DEC_DIGITS_LUT [ pair2 * 2 + 0 ] ) ;
400+ buf. contents [ offset + 3 ] . write( DEC_DIGITS_LUT [ pair2 * 2 + 1 ] ) ;
266401 }
267402
268403 // Format per two digits from the lookup table.
269404 if remain > 9 {
270- // SAFETY: All of the decimals fit in buf due to MAX_DEC_N
405+ // SAFETY: All of the decimals fit in `buf` since `buf` is large enough to
406+ // accommodate the largest representation of a number possilble (that of i128::MIN)
271407 // and the while condition ensures at least 2 more decimals.
272408 unsafe { core:: hint:: assert_unchecked( offset >= 2 ) }
273- // SAFETY: The offset counts down from its initial buf.len()
409+ // SAFETY: The offset counts down from its initial size
274410 // without underflow due to the previous precondition.
275- unsafe { core:: hint:: assert_unchecked( offset <= buf. len( ) ) }
411+ unsafe { core:: hint:: assert_unchecked( offset <= buf. contents . len( ) ) }
276412 offset -= 2 ;
277413
278414 let pair = ( remain % 100 ) as usize ;
279415 remain /= 100 ;
280- buf[ offset + 0 ] . write( DEC_DIGITS_LUT [ pair * 2 + 0 ] ) ;
281- buf[ offset + 1 ] . write( DEC_DIGITS_LUT [ pair * 2 + 1 ] ) ;
416+ buf. contents [ offset + 0 ] . write( DEC_DIGITS_LUT [ pair * 2 + 0 ] ) ;
417+ buf. contents [ offset + 1 ] . write( DEC_DIGITS_LUT [ pair * 2 + 1 ] ) ;
282418 }
283419
284420 // Format the last remaining digit, if any.
285421 if remain != 0 || self == 0 {
286- // SAFETY: All of the decimals fit in buf due to MAX_DEC_N
422+ // SAFETY: All of the decimals fit in `buf` since `buf` is large enough to
423+ // accommodate the largest representation of a number possilble (that of i128::MIN)
287424 // and the if condition ensures (at least) 1 more decimals.
288425 unsafe { core:: hint:: assert_unchecked( offset >= 1 ) }
289- // SAFETY: The offset counts down from its initial buf.len()
426+ // SAFETY: The offset counts down from its initial size
290427 // without underflow due to the previous precondition.
291- unsafe { core:: hint:: assert_unchecked( offset <= buf. len( ) ) }
428+ unsafe { core:: hint:: assert_unchecked( offset <= buf. contents . len( ) ) }
292429 offset -= 1 ;
293430
294431 // Either the compiler sees that remain < 10, or it prevents
295432 // a boundary check up next.
296433 let last = ( remain & 15 ) as usize ;
297- buf[ offset] . write( DEC_DIGITS_LUT [ last * 2 + 1 ] ) ;
434+ buf. contents [ offset] . write( DEC_DIGITS_LUT [ last * 2 + 1 ] ) ;
298435 // not used: remain = 0;
299436 }
300437
301- // SAFETY: All buf content since offset is set.
302- let written = unsafe { buf. get_unchecked( offset..) } ;
303- // SAFETY: Writes use ASCII from the lookup table exclusively.
438+ offset
439+ }
440+
441+ fn _fmt( self , is_nonnegative: bool , f: & mut fmt:: Formatter <' _>) -> fmt:: Result {
442+ // Buffer decimals for $unsigned with right alignment.
443+ let mut buf = NumBuffer :: new( ) ;
444+ let offset = self . _write_into_buf( & mut buf) ;
445+
446+ // SAFETY: All buf contents since offset is set, and
447+ // writes use ASCII from the lookup table exclusively.
304448 let as_str = unsafe {
305- str :: from_utf8_unchecked( slice:: from_raw_parts(
306- MaybeUninit :: slice_as_ptr( written) ,
307- written. len( ) ,
308- ) )
449+ _extract_str_from_buf( & buf, offset)
309450 } ;
451+
310452 f. pad_integral( is_nonnegative, "" , as_str)
311453 }
312454 } ) *
313455
314456 #[ cfg( feature = "optimize_for_size" ) ]
315- fn $gen_name( mut n: $u, is_nonnegative: bool , f: & mut fmt:: Formatter <' _>) -> fmt:: Result {
316- const MAX_DEC_N : usize = $u:: MAX . ilog( 10 ) as usize + 1 ;
317- let mut buf = [ MaybeUninit :: <u8 >:: uninit( ) ; MAX_DEC_N ] ;
318- let mut curr = MAX_DEC_N ;
319- let buf_ptr = MaybeUninit :: slice_as_mut_ptr( & mut buf) ;
457+ fn $gen_name( mut n: $u, is_nonnegative: bool , buf: & mut NumBuffer ) -> & str {
458+ let mut curr = buf. contents. len( ) ;
459+ let buf_ptr = MaybeUninit :: slice_as_mut_ptr( & mut buf. contents) ;
320460
321461 // SAFETY: To show that it's OK to copy into `buf_ptr`, notice that at the beginning
322- // `curr == buf.len() == 39 > log(n)` since `n < 2^128 < 10^39`, and at
462+ // `curr == buf.len() == 41 > log(n)` since `n < 2^128 < 10^39 < 10^41 `, and at
323463 // each step this is kept the same as `n` is divided. Since `n` is always
324- // non-negative, this means that `curr > 0` so `buf_ptr[curr..curr + 1]`
464+ // non-negative, this means that `curr >= (41 - 39) == 2 > 0` so `buf_ptr[curr..curr + 1]`
325465 // is safe to access.
326466 unsafe {
327467 loop {
@@ -335,12 +475,18 @@ macro_rules! impl_Display {
335475 }
336476 }
337477
338- // SAFETY: `curr` > 0 (since we made `buf` large enough), and all the chars are valid UTF-8
478+ // SAFETY: `curr >= 2` (unchanged if `is_nonnegative` is true) and
479+ // `curr >= 1` (incase `is_nonnegative` is false) so `buf_ptr[curr..curr + 1]`
480+ // is safe to access.
481+ unsafe { _add_negative_sign( is_nonnegative, buf, curr) ; }
482+
483+ // SAFETY: `curr >= 1` (since we made `buf` large enough), and all the chars are valid UTF-8
339484 let buf_slice = unsafe {
340485 str :: from_utf8_unchecked(
341486 slice:: from_raw_parts( buf_ptr. add( curr) , buf. len( ) - curr) )
342487 } ;
343- f. pad_integral( is_nonnegative, "" , buf_slice)
488+
489+ buf_slice
344490 }
345491 } ;
346492}
@@ -529,7 +675,7 @@ mod imp {
529675 i32 , u32 ,
530676 i64 , u64 ,
531677 isize , usize ,
532- ; as u64 via to_u64 named fmt_u64
678+ ; as u64 via to_u64 named stringify_u64
533679 ) ;
534680 impl_Exp ! (
535681 i8 , u8 , i16 , u16 , i32 , u32 , i64 , u64 , usize , isize
@@ -545,10 +691,10 @@ mod imp {
545691 i16 , u16 ,
546692 i32 , u32 ,
547693 isize , usize ,
548- ; as u32 via to_u32 named fmt_u32 ) ;
694+ ; as u32 via to_u32 named stringify_u32 ) ;
549695 impl_Display ! (
550696 i64 , u64 ,
551- ; as u64 via to_u64 named fmt_u64 ) ;
697+ ; as u64 via to_u64 named stringify_u64 ) ;
552698
553699 impl_Exp ! ( i8 , u8 , i16 , u16 , i32 , u32 , isize , usize as u32 via to_u32 named exp_u32) ;
554700 impl_Exp ! ( i64 , u64 as u64 via to_u64 named exp_u64) ;
0 commit comments