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,49 @@ 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+ // 3. `buffer.contents` being initialized from `offset` onwards till the end
209+ unsafe fn _extract_str_from_buf ( buf : & NumBuffer , offset : usize ) -> & str {
210+ // SAFETY: safety is ensured by the caller about:
211+ // 1. `offset` being bound checked
212+ // 2. `buffer.contents` being initialized from `offset` onwards
213+ let written = unsafe { buf. contents . get_unchecked ( offset..) } ;
214+
215+ // SAFETY: safety is ensured by the caller about:
216+ // 1. The contents of `buf` containing only ASCII characters
217+ let as_str = unsafe {
218+ str:: from_utf8_unchecked ( slice:: from_raw_parts (
219+ MaybeUninit :: slice_as_ptr ( written) ,
220+ written. len ( ) ,
221+ ) )
222+ } ;
223+
224+ as_str
225+ }
226+
227+ // SAFETY: safety is ensured by the caller about:
228+ // 1. `start_offset` being bound checked
229+ unsafe fn _add_negative_sign (
230+ is_nonnegative : bool ,
231+ buf : & mut NumBuffer ,
232+ start_offset : usize ,
233+ ) -> usize {
234+ if is_nonnegative {
235+ return start_offset;
236+ }
237+
238+ let offset = start_offset - 1 ;
239+
240+ // Setting sign for the negative number
241+ buf. contents [ offset] . write ( NEGATIVE_SIGN [ 0 ] ) ;
242+
243+ offset
244+ }
245+
202246macro_rules! impl_Display {
203247 ( $( $signed: ident, $unsigned: ident, ) * ; as $u: ident via $conv_fn: ident named $gen_name: ident) => {
204248
@@ -212,7 +256,9 @@ macro_rules! impl_Display {
212256 }
213257 #[ cfg( feature = "optimize_for_size" ) ]
214258 {
215- $gen_name( self . $conv_fn( ) , true , f)
259+ let mut buf = NumBuffer :: new( ) ;
260+ let as_str = $gen_name( self . $conv_fn( ) , true , & mut buf) ;
261+ f. pad_integral( true , "" , as_str)
216262 }
217263 }
218264 }
@@ -226,31 +272,122 @@ macro_rules! impl_Display {
226272 }
227273 #[ cfg( feature = "optimize_for_size" ) ]
228274 {
229- return $gen_name( self . unsigned_abs( ) . $conv_fn( ) , * self >= 0 , f) ;
275+ let mut buf = NumBuffer :: new( ) ;
276+
277+ // not setting the sign here, hence sending in `true`
278+ let as_str = $gen_name( self . unsigned_abs( ) . $conv_fn( ) , true , & mut buf) ;
279+ f. pad_integral( * self >= 0 , "" , as_str)
230280 }
231281 }
232282 }
233283
284+ #[ unstable( feature = "int_format_into" , issue = "138215" ) ]
285+ impl $signed {
286+ /// Allows users to write an integer (in signed decimal format) into a variable `buf` of
287+ /// type [`NumBuffer`] that is passed by the caller by mutable reference.
288+ ///
289+ /// # Examples
290+ /// ```
291+ /// #![feature(int_format_into)]
292+ /// use core::fmt::NumBuffer;
293+ ///
294+ #[ doc = concat!( "let n = -32" , stringify!( $signed) , ";" ) ]
295+ /// let mut buf = NumBuffer::new();
296+ /// assert_eq!(n.format_into(&mut buf), "-32");
297+ ///
298+ #[ doc = concat!( "let n2 = " , stringify!( $signed:: MIN ) , ";" ) ]
299+ /// let mut buf2 = NumBuffer::new();
300+ #[ doc = concat!( "assert_eq!(n2.format_into(&mut buf2), " , stringify!( $signed:: MIN ) , ".to_string());" ) ]
301+ ///
302+ #[ doc = concat!( "let n3 = " , stringify!( $signed:: MAX ) , ";" ) ]
303+ /// let mut buf3 = NumBuffer::new();
304+ #[ doc = concat!( "assert_eq!(n3.format_into(&mut buf3), " , stringify!( $signed:: MAX ) , ".to_string());" ) ]
305+ /// ```
306+ ///
307+ pub fn format_into( self , buf: & mut NumBuffer ) -> & str {
308+ #[ cfg( not( feature = "optimize_for_size" ) ) ]
309+ {
310+ let is_nonnegative = self > 0 ;
311+ let offset = self . unsigned_abs( ) . _write_into_buf( buf) ;
312+
313+ // SAFETY: `offset >= 2` (unchanged if `is_nonnegative` is true) and
314+ // `offset >= 1` (incase `is_nonnegative` is false) so
315+ // `buf_ptr[offset..offset + 1]` is safe to access.
316+ unsafe { _add_negative_sign( is_nonnegative, buf, offset) ; }
317+
318+ // SAFETY: All buf content since offset is set, and
319+ // writes use ASCII from the lookup table exclusively.
320+ let as_str = unsafe { _extract_str_from_buf( buf, offset) } ;
321+
322+ as_str
323+ }
324+
325+ #[ cfg( feature = "optimize_for_size" ) ]
326+ {
327+ let is_nonnegative = self > 0 ;
328+ $gen_name( self . unsigned_abs( ) , is_nonnegative, buf)
329+ }
330+
331+ }
332+ }
333+
334+ #[ unstable( feature = "int_format_into" , issue = "138215" ) ]
335+ impl $unsigned {
336+ /// Allows users to write an integer (in signed decimal format) into a variable `buf` of
337+ /// type [`NumBuffer`] that is passed by the caller by mutable reference.
338+ ///
339+ /// # Examples
340+ /// ```
341+ /// #![feature(int_format_into)]
342+ /// use core::fmt::NumBuffer;
343+ ///
344+ #[ doc = concat!( "let n = 32" , stringify!( $unsigned) , ";" ) ]
345+ /// let mut buf = NumBuffer::new();
346+ /// assert_eq!(n.format_into(&mut buf), "32");
347+ ///
348+ #[ doc = concat!( "let n2 = " , stringify!( $unsigned:: MAX ) , ";" ) ]
349+ /// let mut buf2 = NumBuffer::new();
350+ #[ doc = concat!( "assert_eq!(n2.format_into(&mut buf2), " , stringify!( $unsigned:: MAX ) , ".to_string());" ) ]
351+ /// ```
352+ ///
353+ pub fn format_into( self , buf: & mut NumBuffer ) -> & str {
354+ #[ cfg( not( feature = "optimize_for_size" ) ) ]
355+ {
356+ let offset = self . _write_into_buf( buf) ;
357+
358+ // SAFETY: All contents in `buf` since offset is set, and
359+ // writes use ASCII from the lookup table exclusively.
360+ let as_str = unsafe { _extract_str_from_buf( buf, offset) } ;
361+
362+ as_str
363+ }
364+
365+ #[ cfg( feature = "optimize_for_size" ) ]
366+ {
367+ $gen_name( self , true , buf)
368+ }
369+
370+ }
371+ }
372+
234373 #[ cfg( not( feature = "optimize_for_size" ) ) ]
235374 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 ] ;
375+ fn _write_into_buf( self , buf: & mut NumBuffer ) -> usize {
240376 // Count the number of bytes in buf that are not initialized.
241- let mut offset = buf. len( ) ;
377+ let mut offset = buf. contents . len( ) ;
242378 // Consume the least-significant decimals from a working copy.
243379 let mut remain = self ;
244380
245381 // Format per four digits from the lookup table.
246382 // Four digits need a 16-bit $unsigned or wider.
247383 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
384+ // SAFETY: All of the decimals fit in `buf` since `buf` is large enough to
385+ // accommodate the largest representation of a number possible (that of i128::MIN)
249386 // and the while condition ensures at least 4 more decimals.
250387 unsafe { core:: hint:: assert_unchecked( offset >= 4 ) }
251- // SAFETY: The offset counts down from its initial buf.len()
388+ // SAFETY: The offset counts down from its initial size
252389 // without underflow due to the previous precondition.
253- unsafe { core:: hint:: assert_unchecked( offset <= buf. len( ) ) }
390+ unsafe { core:: hint:: assert_unchecked( offset <= buf. contents . len( ) ) }
254391 offset -= 4 ;
255392
256393 // pull two pairs
@@ -259,69 +396,74 @@ macro_rules! impl_Display {
259396 remain /= scale;
260397 let pair1 = ( quad / 100 ) as usize ;
261398 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 ] ) ;
399+ buf. contents [ offset + 0 ] . write( DEC_DIGITS_LUT [ pair1 * 2 + 0 ] ) ;
400+ buf. contents [ offset + 1 ] . write( DEC_DIGITS_LUT [ pair1 * 2 + 1 ] ) ;
401+ buf. contents [ offset + 2 ] . write( DEC_DIGITS_LUT [ pair2 * 2 + 0 ] ) ;
402+ buf. contents [ offset + 3 ] . write( DEC_DIGITS_LUT [ pair2 * 2 + 1 ] ) ;
266403 }
267404
268405 // Format per two digits from the lookup table.
269406 if remain > 9 {
270- // SAFETY: All of the decimals fit in buf due to MAX_DEC_N
407+ // SAFETY: All of the decimals fit in `buf` since `buf` is large enough to
408+ // accommodate the largest representation of a number possilble (that of i128::MIN)
271409 // and the while condition ensures at least 2 more decimals.
272410 unsafe { core:: hint:: assert_unchecked( offset >= 2 ) }
273- // SAFETY: The offset counts down from its initial buf.len()
411+ // SAFETY: The offset counts down from its initial size
274412 // without underflow due to the previous precondition.
275- unsafe { core:: hint:: assert_unchecked( offset <= buf. len( ) ) }
413+ unsafe { core:: hint:: assert_unchecked( offset <= buf. contents . len( ) ) }
276414 offset -= 2 ;
277415
278416 let pair = ( remain % 100 ) as usize ;
279417 remain /= 100 ;
280- buf[ offset + 0 ] . write( DEC_DIGITS_LUT [ pair * 2 + 0 ] ) ;
281- buf[ offset + 1 ] . write( DEC_DIGITS_LUT [ pair * 2 + 1 ] ) ;
418+ buf. contents [ offset + 0 ] . write( DEC_DIGITS_LUT [ pair * 2 + 0 ] ) ;
419+ buf. contents [ offset + 1 ] . write( DEC_DIGITS_LUT [ pair * 2 + 1 ] ) ;
282420 }
283421
284422 // Format the last remaining digit, if any.
285423 if remain != 0 || self == 0 {
286- // SAFETY: All of the decimals fit in buf due to MAX_DEC_N
424+ // SAFETY: All of the decimals fit in `buf` since `buf` is large enough to
425+ // accommodate the largest representation of a number possilble (that of i128::MIN)
287426 // and the if condition ensures (at least) 1 more decimals.
288427 unsafe { core:: hint:: assert_unchecked( offset >= 1 ) }
289- // SAFETY: The offset counts down from its initial buf.len()
428+ // SAFETY: The offset counts down from its initial size
290429 // without underflow due to the previous precondition.
291- unsafe { core:: hint:: assert_unchecked( offset <= buf. len( ) ) }
430+ unsafe { core:: hint:: assert_unchecked( offset <= buf. contents . len( ) ) }
292431 offset -= 1 ;
293432
294433 // Either the compiler sees that remain < 10, or it prevents
295434 // a boundary check up next.
296435 let last = ( remain & 15 ) as usize ;
297- buf[ offset] . write( DEC_DIGITS_LUT [ last * 2 + 1 ] ) ;
436+ buf. contents [ offset] . write( DEC_DIGITS_LUT [ last * 2 + 1 ] ) ;
298437 // not used: remain = 0;
299438 }
300439
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.
440+ offset
441+ }
442+
443+ fn _fmt( self , is_nonnegative: bool , f: & mut fmt:: Formatter <' _>) -> fmt:: Result {
444+ // Buffer decimals for $unsigned with right alignment.
445+ let mut buf = NumBuffer :: new( ) ;
446+ let offset = self . _write_into_buf( & mut buf) ;
447+
448+ // SAFETY: All buf contents since offset is set, and
449+ // writes use ASCII from the lookup table exclusively.
304450 let as_str = unsafe {
305- str :: from_utf8_unchecked( slice:: from_raw_parts(
306- MaybeUninit :: slice_as_ptr( written) ,
307- written. len( ) ,
308- ) )
451+ _extract_str_from_buf( & buf, offset)
309452 } ;
453+
310454 f. pad_integral( is_nonnegative, "" , as_str)
311455 }
312456 } ) *
313457
314458 #[ 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) ;
459+ fn $gen_name( mut n: $u, is_nonnegative: bool , buf: & mut NumBuffer ) -> & str {
460+ let mut curr = buf. contents. len( ) ;
461+ let buf_ptr = MaybeUninit :: slice_as_mut_ptr( & mut buf. contents) ;
320462
321463 // 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
464+ // `curr == buf.len() == 41 > log(n)` since `n < 2^128 < 10^39 < 10^41 `, and at
323465 // 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]`
466+ // non-negative, this means that `curr >= (41 - 39) == 2 > 0` so `buf_ptr[curr..curr + 1]`
325467 // is safe to access.
326468 unsafe {
327469 loop {
@@ -335,12 +477,18 @@ macro_rules! impl_Display {
335477 }
336478 }
337479
338- // SAFETY: `curr` > 0 (since we made `buf` large enough), and all the chars are valid UTF-8
480+ // SAFETY: `curr >= 2` (unchanged if `is_nonnegative` is true) and
481+ // `curr >= 1` (incase `is_nonnegative` is false) so `buf_ptr[curr..curr + 1]`
482+ // is safe to access.
483+ unsafe { _add_negative_sign( is_nonnegative, buf, curr) ; }
484+
485+ // SAFETY: `curr >= 1` (since we made `buf` large enough), and all the chars are valid UTF-8
339486 let buf_slice = unsafe {
340487 str :: from_utf8_unchecked(
341488 slice:: from_raw_parts( buf_ptr. add( curr) , buf. len( ) - curr) )
342489 } ;
343- f. pad_integral( is_nonnegative, "" , buf_slice)
490+
491+ buf_slice
344492 }
345493 } ;
346494}
@@ -529,7 +677,7 @@ mod imp {
529677 i32 , u32 ,
530678 i64 , u64 ,
531679 isize , usize ,
532- ; as u64 via to_u64 named fmt_u64
680+ ; as u64 via to_u64 named stringify_u64
533681 ) ;
534682 impl_Exp ! (
535683 i8 , u8 , i16 , u16 , i32 , u32 , i64 , u64 , usize , isize
@@ -545,10 +693,10 @@ mod imp {
545693 i16 , u16 ,
546694 i32 , u32 ,
547695 isize , usize ,
548- ; as u32 via to_u32 named fmt_u32 ) ;
696+ ; as u32 via to_u32 named stringify_u32 ) ;
549697 impl_Display ! (
550698 i64 , u64 ,
551- ; as u64 via to_u64 named fmt_u64 ) ;
699+ ; as u64 via to_u64 named stringify_u64 ) ;
552700
553701 impl_Exp ! ( i8 , u8 , i16 , u16 , i32 , u32 , isize , usize as u32 via to_u32 named exp_u32) ;
554702 impl_Exp ! ( i64 , u64 as u64 via to_u64 named exp_u64) ;
0 commit comments