diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index 7d41ae45093ea..fa64240cfdbf3 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -3,164 +3,82 @@ use crate::fmt::NumBuffer; use crate::mem::MaybeUninit; use crate::num::fmt as numfmt; -use crate::ops::{Div, Rem, Sub}; use crate::{fmt, ptr, slice, str}; -#[doc(hidden)] -trait DisplayInt: - PartialEq + PartialOrd + Div + Rem + Sub + Copy -{ - fn zero() -> Self; - fn from_u8(u: u8) -> Self; - fn to_u8(&self) -> u8; - #[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))] - fn to_u32(&self) -> u32; - fn to_u64(&self) -> u64; - fn to_u128(&self) -> u128; -} +// Formatting of integers with a non-decimal radix. +macro_rules! radix_integer { + (fmt::$Trait:ident for $Signed:ident and $Unsigned:ident, $prefix:expr, $dig_tab:expr) => { + #[stable(feature = "rust1", since = "1.0.0")] + impl fmt::$Trait for $Unsigned { + /// Format unsigned integers in the radix. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Check arguments at compile time. + assert!($Unsigned::MIN == 0); + $dig_tab.as_ascii().unwrap(); -macro_rules! impl_int { - ($($t:ident)*) => ( - $(impl DisplayInt for $t { - fn zero() -> Self { 0 } - fn from_u8(u: u8) -> Self { u as Self } - fn to_u8(&self) -> u8 { *self as u8 } - #[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))] - fn to_u32(&self) -> u32 { *self as u32 } - fn to_u64(&self) -> u64 { *self as u64 } - fn to_u128(&self) -> u128 { *self as u128 } - })* - ) -} + // ASCII digits in ascending order are used as a lookup table. + const DIG_TAB: &[u8] = $dig_tab; + const BASE: $Unsigned = DIG_TAB.len() as $Unsigned; + const MAX_DIG_N: usize = $Unsigned::MAX.ilog(BASE) as usize + 1; -impl_int! { - i8 i16 i32 i64 i128 isize - u8 u16 u32 u64 u128 usize -} + // Buffer digits of self with right alignment. + let mut buf = [MaybeUninit::::uninit(); MAX_DIG_N]; + // Count the number of bytes in buf that are not initialized. + let mut offset = buf.len(); -/// A type that represents a specific radix -/// -/// # Safety -/// -/// `digit` must return an ASCII character. -#[doc(hidden)] -unsafe trait GenericRadix: Sized { - /// The number of digits. - const BASE: u8; - - /// A radix-specific prefix string. - const PREFIX: &'static str; - - /// Converts an integer to corresponding radix digit. - fn digit(x: u8) -> u8; - - /// Format an integer using the radix using a formatter. - fn fmt_int(&self, mut x: T, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // The radix can be as low as 2, so we need a buffer of at least 128 - // characters for a base 2 number. - let zero = T::zero(); - let is_nonnegative = x >= zero; - let mut buf = [MaybeUninit::::uninit(); 128]; - let mut offset = buf.len(); - let base = T::from_u8(Self::BASE); - if is_nonnegative { - // Accumulate each digit of the number from the least significant - // to the most significant figure. - loop { - let n = x % base; // Get the current place value. - x = x / base; // Deaccumulate the number. - offset -= 1; - buf[offset].write(Self::digit(n.to_u8())); // Store the digit in the buffer. - if x == zero { - // No more digits left to accumulate. - break; - }; - } - } else { - // Do the same as above, but accounting for two's complement. - loop { - let n = zero - (x % base); // Get the current place value. - x = x / base; // Deaccumulate the number. - offset -= 1; - buf[offset].write(Self::digit(n.to_u8())); // Store the digit in the buffer. - if x == zero { - // No more digits left to accumulate. - break; - }; - } - } - // SAFETY: Starting from `offset`, all elements of the slice have been set. - let buf_slice = unsafe { slice_buffer_to_str(&buf, offset) }; - f.pad_integral(is_nonnegative, Self::PREFIX, buf_slice) - } -} + // Accumulate each digit of the number from the least + // significant to the most significant figure. + let mut remain = *self; + loop { + let digit = remain % BASE; + remain /= BASE; -/// A binary (base 2) radix -#[derive(Clone, PartialEq)] -struct Binary; - -/// An octal (base 8) radix -#[derive(Clone, PartialEq)] -struct Octal; - -/// A hexadecimal (base 16) radix, formatted with lower-case characters -#[derive(Clone, PartialEq)] -struct LowerHex; - -/// A hexadecimal (base 16) radix, formatted with upper-case characters -#[derive(Clone, PartialEq)] -struct UpperHex; - -macro_rules! radix { - ($T:ident, $base:expr, $prefix:expr, $($x:pat => $conv:expr),+) => { - unsafe impl GenericRadix for $T { - const BASE: u8 = $base; - const PREFIX: &'static str = $prefix; - fn digit(x: u8) -> u8 { - match x { - $($x => $conv,)+ - x => panic!("number not in the range 0..={}: {}", Self::BASE - 1, x), + // SAFETY: All of the decimals fit in buf due to MAX_DEC_N + // and the break condition below ensures at least 1 more + // decimal. + unsafe { core::hint::assert_unchecked(offset >= 1) } + // SAFETY: The offset counts down from its initial buf.len() + // without underflow due to the previous precondition. + unsafe { core::hint::assert_unchecked(offset <= buf.len()) } + offset -= 1; + buf[offset].write(DIG_TAB[digit as usize]); + if remain == 0 { + break; + } } + + // SAFETY: Starting from `offset`, all elements of the slice have been set. + let digits = unsafe { slice_buffer_to_str(&buf, offset) }; + f.pad_integral(true, $prefix, digits) } } - } -} -radix! { Binary, 2, "0b", x @ 0 ..= 1 => b'0' + x } -radix! { Octal, 8, "0o", x @ 0 ..= 7 => b'0' + x } -radix! { LowerHex, 16, "0x", x @ 0 ..= 9 => b'0' + x, x @ 10 ..= 15 => b'a' + (x - 10) } -radix! { UpperHex, 16, "0x", x @ 0 ..= 9 => b'0' + x, x @ 10 ..= 15 => b'A' + (x - 10) } - -macro_rules! int_base { - (fmt::$Trait:ident for $T:ident as $U:ident -> $Radix:ident) => { #[stable(feature = "rust1", since = "1.0.0")] - impl fmt::$Trait for $T { + impl fmt::$Trait for $Signed { + /// Format signed integers in the two’s-complement form. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - $Radix.fmt_int(*self as $U, f) + assert!($Signed::MIN < 0); + fmt::$Trait::fmt(&(*self as $Unsigned), f) } } }; } -macro_rules! integer { - ($Int:ident, $Uint:ident) => { - int_base! { fmt::Binary for $Int as $Uint -> Binary } - int_base! { fmt::Octal for $Int as $Uint -> Octal } - int_base! { fmt::LowerHex for $Int as $Uint -> LowerHex } - int_base! { fmt::UpperHex for $Int as $Uint -> UpperHex } - - int_base! { fmt::Binary for $Uint as $Uint -> Binary } - int_base! { fmt::Octal for $Uint as $Uint -> Octal } - int_base! { fmt::LowerHex for $Uint as $Uint -> LowerHex } - int_base! { fmt::UpperHex for $Uint as $Uint -> UpperHex } +// Formatting of integers with a non-decimal radix. +macro_rules! radix_integers { + ($Signed:ident, $Unsigned:ident) => { + radix_integer! { fmt::Binary for $Signed and $Unsigned, "0b", b"01" } + radix_integer! { fmt::Octal for $Signed and $Unsigned, "0o", b"01234567" } + radix_integer! { fmt::LowerHex for $Signed and $Unsigned, "0x", b"0123456789abcdef" } + radix_integer! { fmt::UpperHex for $Signed and $Unsigned, "0x", b"0123456789ABCDEF" } }; } -integer! { isize, usize } -integer! { i8, u8 } -integer! { i16, u16 } -integer! { i32, u32 } -integer! { i64, u64 } -integer! { i128, u128 } +radix_integers! { isize, usize } +radix_integers! { i8, u8 } +radix_integers! { i16, u16 } +radix_integers! { i32, u32 } +radix_integers! { i64, u64 } +radix_integers! { i128, u128 } macro_rules! impl_Debug { ($($T:ident)*) => { @@ -205,16 +123,16 @@ unsafe fn slice_buffer_to_str(buf: &[MaybeUninit], offset: usize) -> &str { } macro_rules! impl_Display { - ($($signed:ident, $unsigned:ident,)* ; as $u:ident via $conv_fn:ident named $gen_name:ident) => { + ($($Signed:ident, $Unsigned:ident),* ; as $T:ident into $fmt_fn:ident) => { $( #[stable(feature = "rust1", since = "1.0.0")] - impl fmt::Display for $unsigned { + impl fmt::Display for $Unsigned { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "optimize_for_size"))] { - const MAX_DEC_N: usize = $unsigned::MAX.ilog10() as usize + 1; - // Buffer decimals for $unsigned with right alignment. + const MAX_DEC_N: usize = $Unsigned::MAX.ilog10() as usize + 1; + // Buffer decimals for self with right alignment. let mut buf = [MaybeUninit::::uninit(); MAX_DEC_N]; // SAFETY: `buf` is always big enough to contain all the digits. @@ -222,18 +140,18 @@ macro_rules! impl_Display { } #[cfg(feature = "optimize_for_size")] { - $gen_name(self.$conv_fn(), true, f) + $fmt_fn(self as $T, true, f) } } } #[stable(feature = "rust1", since = "1.0.0")] - impl fmt::Display for $signed { + impl fmt::Display for $Signed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "optimize_for_size"))] { - const MAX_DEC_N: usize = $unsigned::MAX.ilog10() as usize + 1; - // Buffer decimals for $unsigned with right alignment. + const MAX_DEC_N: usize = $Unsigned::MAX.ilog10() as usize + 1; + // Buffer decimals for self with right alignment. let mut buf = [MaybeUninit::::uninit(); MAX_DEC_N]; // SAFETY: `buf` is always big enough to contain all the digits. @@ -241,13 +159,13 @@ macro_rules! impl_Display { } #[cfg(feature = "optimize_for_size")] { - return $gen_name(self.unsigned_abs().$conv_fn(), *self >= 0, f); + return $fmt_fn(self.unsigned_abs() as $T, *self >= 0, f); } } } #[cfg(not(feature = "optimize_for_size"))] - impl $unsigned { + impl $Unsigned { #[doc(hidden)] #[unstable( feature = "fmt_internals", @@ -268,7 +186,7 @@ macro_rules! impl_Display { let mut remain = self; // Format per four digits from the lookup table. - // Four digits need a 16-bit $unsigned or wider. + // Four digits need a 16-bit $Unsigned or wider. while size_of::() > 1 && remain > 999.try_into().expect("branch is not hit for types that cannot fit 999 (u8)") { // SAFETY: All of the decimals fit in buf due to MAX_DEC_N // and the while condition ensures at least 4 more decimals. @@ -327,7 +245,7 @@ macro_rules! impl_Display { } } - impl $signed { + impl $Signed { /// Allows users to write an integer (in signed decimal format) into a variable `buf` of /// type [`NumBuffer`] that is passed by the caller by mutable reference. /// @@ -337,15 +255,15 @@ macro_rules! impl_Display { /// #![feature(int_format_into)] /// use core::fmt::NumBuffer; /// - #[doc = concat!("let n = 0", stringify!($signed), ";")] + #[doc = concat!("let n = 0", stringify!($Signed), ";")] /// let mut buf = NumBuffer::new(); /// assert_eq!(n.format_into(&mut buf), "0"); /// - #[doc = concat!("let n1 = 32", stringify!($signed), ";")] + #[doc = concat!("let n1 = 32", stringify!($Signed), ";")] /// assert_eq!(n1.format_into(&mut buf), "32"); /// - #[doc = concat!("let n2 = ", stringify!($signed::MAX), ";")] - #[doc = concat!("assert_eq!(n2.format_into(&mut buf), ", stringify!($signed::MAX), ".to_string());")] + #[doc = concat!("let n2 = ", stringify!($Signed::MAX), ";")] + #[doc = concat!("assert_eq!(n2.format_into(&mut buf), ", stringify!($Signed::MAX), ".to_string());")] /// ``` #[unstable(feature = "int_format_into", issue = "138215")] pub fn format_into(self, buf: &mut NumBuffer) -> &str { @@ -358,7 +276,7 @@ macro_rules! impl_Display { } #[cfg(feature = "optimize_for_size")] { - offset = ${concat(_inner_slow_integer_to_str, $gen_name)}(self.unsigned_abs().$conv_fn(), &mut buf.buf); + offset = ${concat(_inner_slow_integer_to_str, $fmt_fn)}(self.unsigned_abs() as $T, &mut buf.buf); } // Only difference between signed and unsigned are these 4 lines. if self < 0 { @@ -370,7 +288,7 @@ macro_rules! impl_Display { } } - impl $unsigned { + impl $Unsigned { /// Allows users to write an integer (in signed decimal format) into a variable `buf` of /// type [`NumBuffer`] that is passed by the caller by mutable reference. /// @@ -380,15 +298,15 @@ macro_rules! impl_Display { /// #![feature(int_format_into)] /// use core::fmt::NumBuffer; /// - #[doc = concat!("let n = 0", stringify!($unsigned), ";")] + #[doc = concat!("let n = 0", stringify!($Unsigned), ";")] /// let mut buf = NumBuffer::new(); /// assert_eq!(n.format_into(&mut buf), "0"); /// - #[doc = concat!("let n1 = 32", stringify!($unsigned), ";")] + #[doc = concat!("let n1 = 32", stringify!($Unsigned), ";")] /// assert_eq!(n1.format_into(&mut buf), "32"); /// - #[doc = concat!("let n2 = ", stringify!($unsigned::MAX), ";")] - #[doc = concat!("assert_eq!(n2.format_into(&mut buf), ", stringify!($unsigned::MAX), ".to_string());")] + #[doc = concat!("let n2 = ", stringify!($Unsigned::MAX), ";")] + #[doc = concat!("assert_eq!(n2.format_into(&mut buf), ", stringify!($Unsigned::MAX), ".to_string());")] /// ``` #[unstable(feature = "int_format_into", issue = "138215")] pub fn format_into(self, buf: &mut NumBuffer) -> &str { @@ -401,7 +319,7 @@ macro_rules! impl_Display { } #[cfg(feature = "optimize_for_size")] { - offset = ${concat(_inner_slow_integer_to_str, $gen_name)}(self.$conv_fn(), &mut buf.buf); + offset = ${concat(_inner_slow_integer_to_str, $fmt_fn)}(*self as $T, &mut buf.buf); } // SAFETY: Starting from `offset`, all elements of the slice have been set. unsafe { slice_buffer_to_str(&buf.buf, offset) } @@ -412,7 +330,7 @@ macro_rules! impl_Display { )* #[cfg(feature = "optimize_for_size")] - fn ${concat(_inner_slow_integer_to_str, $gen_name)}(mut n: $u, buf: &mut [MaybeUninit::]) -> usize { + fn ${concat(_inner_slow_integer_to_str, $fmt_fn)}(mut n: $T, buf: &mut [MaybeUninit::]) -> usize { let mut curr = buf.len(); // SAFETY: To show that it's OK to copy into `buf_ptr`, notice that at the beginning @@ -433,11 +351,11 @@ macro_rules! impl_Display { } #[cfg(feature = "optimize_for_size")] - fn $gen_name(n: $u, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result { - const MAX_DEC_N: usize = $u::MAX.ilog(10) as usize + 1; + fn $fmt_fn(n: $T, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result { + const MAX_DEC_N: usize = $T::MAX.ilog(10) as usize + 1; let mut buf = [MaybeUninit::::uninit(); MAX_DEC_N]; - let offset = ${concat(_inner_slow_integer_to_str, $gen_name)}(n, &mut buf); + let offset = ${concat(_inner_slow_integer_to_str, $fmt_fn)}(n, &mut buf); // SAFETY: Starting from `offset`, all elements of the slice have been set. let buf_slice = unsafe { slice_buffer_to_str(&buf, offset) }; f.pad_integral(is_nonnegative, "", buf_slice) @@ -446,9 +364,9 @@ macro_rules! impl_Display { } macro_rules! impl_Exp { - ($($t:ident),* as $u:ident via $conv_fn:ident named $name:ident) => { - fn $name( - mut n: $u, + ($($Signed:ident, $Unsigned:ident),* ; as $T:ident into $fmt_fn:ident) => { + fn $fmt_fn( + mut n: $T, is_nonnegative: bool, upper: bool, f: &mut fmt::Formatter<'_> @@ -582,32 +500,41 @@ macro_rules! impl_Exp { $( #[stable(feature = "integer_exp_format", since = "1.42.0")] - impl fmt::LowerExp for $t { - #[allow(unused_comparisons)] + impl fmt::LowerExp for $Signed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let is_nonnegative = *self >= 0; let n = if is_nonnegative { - self.$conv_fn() + *self as $T } else { - // convert the negative num to positive by summing 1 to its 2s complement - (!self.$conv_fn()).wrapping_add(1) + self.unsigned_abs() as $T }; - $name(n, is_nonnegative, false, f) + $fmt_fn(n, is_nonnegative, false, f) + } + } + #[stable(feature = "integer_exp_format", since = "1.42.0")] + impl fmt::LowerExp for $Unsigned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $fmt_fn(*self as $T, true, false, f) } })* + $( #[stable(feature = "integer_exp_format", since = "1.42.0")] - impl fmt::UpperExp for $t { - #[allow(unused_comparisons)] + impl fmt::UpperExp for $Signed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let is_nonnegative = *self >= 0; let n = if is_nonnegative { - self.$conv_fn() + *self as $T } else { - // convert the negative num to positive by summing 1 to its 2s complement - (!self.$conv_fn()).wrapping_add(1) + self.unsigned_abs() as $T }; - $name(n, is_nonnegative, true, f) + $fmt_fn(n, is_nonnegative, true, f) + } + } + #[stable(feature = "integer_exp_format", since = "1.42.0")] + impl fmt::UpperExp for $Unsigned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + $fmt_fn(*self as $T, true, true, f) } })* }; @@ -623,37 +550,20 @@ impl_Debug! { #[cfg(any(target_pointer_width = "64", target_arch = "wasm32"))] mod imp { use super::*; - impl_Display!( - i8, u8, - i16, u16, - i32, u32, - i64, u64, - isize, usize, - ; as u64 via to_u64 named fmt_u64 - ); - impl_Exp!( - i8, u8, i16, u16, i32, u32, i64, u64, usize, isize - as u64 via to_u64 named exp_u64 - ); + impl_Display!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize; as u64 into fmt_u64); + impl_Exp!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize; as u64 into exp_u64); } #[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))] mod imp { use super::*; - impl_Display!( - i8, u8, - i16, u16, - i32, u32, - isize, usize, - ; as u32 via to_u32 named fmt_u32); - impl_Display!( - i64, u64, - ; as u64 via to_u64 named fmt_u64); - - impl_Exp!(i8, u8, i16, u16, i32, u32, isize, usize as u32 via to_u32 named exp_u32); - impl_Exp!(i64, u64 as u64 via to_u64 named exp_u64); + impl_Display!(i8, u8, i16, u16, i32, u32, isize, usize; as u32 into fmt_u32); + impl_Display!(i64, u64; as u64 into fmt_u64); + + impl_Exp!(i8, u8, i16, u16, i32, u32, isize, usize; as u32 into exp_u32); + impl_Exp!(i64, u64; as u64 into exp_u64); } -impl_Exp!(i128, u128 as u128 via to_u128 named exp_u128); +impl_Exp!(i128, u128; as u128 into exp_u128); const U128_MAX_DEC_N: usize = u128::MAX.ilog10() as usize + 1; diff --git a/library/coretests/benches/fmt.rs b/library/coretests/benches/fmt.rs index ee8e981b46b97..851f39e53e7f6 100644 --- a/library/coretests/benches/fmt.rs +++ b/library/coretests/benches/fmt.rs @@ -162,3 +162,60 @@ fn write_u8_min(bh: &mut Bencher) { black_box(format!("{}", black_box(u8::MIN))); }); } + +#[bench] +fn write_10ints_bin(bh: &mut Bencher) { + bh.iter(|| { + black_box(format!("{:b}", black_box(u8::MIN))); + black_box(format!("{:b}", black_box(i8::MIN))); + black_box(format!("{:b}", black_box(i8::MAX))); + + black_box(format!("{:b}", black_box(u32::MIN))); + black_box(format!("{:b}", black_box(i32::MIN))); + black_box(format!("{:b}", black_box(i32::MAX))); + + black_box(format!("{:b}", black_box(u64::MIN))); + black_box(format!("{:b}", black_box(i64::MIN))); + black_box(format!("{:b}", black_box(i64::MAX))); + + black_box(format!("{:b}", black_box(42_usize))); + }); +} + +#[bench] +fn write_10ints_oct(bh: &mut Bencher) { + bh.iter(|| { + black_box(format!("{:o}", black_box(u8::MIN))); + black_box(format!("{:o}", black_box(i8::MIN))); + black_box(format!("{:o}", black_box(i8::MAX))); + + black_box(format!("{:o}", black_box(u32::MIN))); + black_box(format!("{:o}", black_box(i32::MIN))); + black_box(format!("{:o}", black_box(i32::MAX))); + + black_box(format!("{:o}", black_box(u64::MIN))); + black_box(format!("{:o}", black_box(i64::MIN))); + black_box(format!("{:o}", black_box(i64::MAX))); + + black_box(format!("{:o}", black_box(42_usize))); + }); +} + +#[bench] +fn write_10ints_hex(bh: &mut Bencher) { + bh.iter(|| { + black_box(format!("{:x}", black_box(u8::MIN))); + black_box(format!("{:x}", black_box(i8::MIN))); + black_box(format!("{:x}", black_box(i8::MAX))); + + black_box(format!("{:x}", black_box(u32::MIN))); + black_box(format!("{:x}", black_box(i32::MIN))); + black_box(format!("{:x}", black_box(i32::MAX))); + + black_box(format!("{:x}", black_box(u64::MIN))); + black_box(format!("{:x}", black_box(i64::MIN))); + black_box(format!("{:x}", black_box(i64::MAX))); + + black_box(format!("{:x}", black_box(42_usize))); + }); +}