Skip to content

Commit c8e628c

Browse files
committed
fmt with table lookup for binary, octal and hex
* correct buffer size * no trait abstraction * similar to decimal
1 parent 9d9a80c commit c8e628c

File tree

1 file changed

+66
-132
lines changed

1 file changed

+66
-132
lines changed

library/core/src/fmt/num.rs

Lines changed: 66 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ use crate::{fmt, ptr, slice, str};
99
trait DisplayInt:
1010
PartialEq + PartialOrd + Div<Output = Self> + Rem<Output = Self> + Sub<Output = Self> + Copy
1111
{
12-
fn zero() -> Self;
13-
fn from_u8(u: u8) -> Self;
14-
fn to_u8(&self) -> u8;
1512
#[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))]
1613
fn to_u32(&self) -> u32;
1714
fn to_u64(&self) -> u64;
@@ -21,9 +18,6 @@ trait DisplayInt:
2118
macro_rules! impl_int {
2219
($($t:ident)*) => (
2320
$(impl DisplayInt for $t {
24-
fn zero() -> Self { 0 }
25-
fn from_u8(u: u8) -> Self { u as Self }
26-
fn to_u8(&self) -> u8 { *self as u8 }
2721
#[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))]
2822
fn to_u32(&self) -> u32 { *self as u32 }
2923
fn to_u64(&self) -> u64 { *self as u64 }
@@ -37,147 +31,87 @@ impl_int! {
3731
u8 u16 u32 u64 u128 usize
3832
}
3933

40-
/// A type that represents a specific radix
41-
///
42-
/// # Safety
43-
///
44-
/// `digit` must return an ASCII character.
45-
#[doc(hidden)]
46-
unsafe trait GenericRadix: Sized {
47-
/// The number of digits.
48-
const BASE: u8;
49-
50-
/// A radix-specific prefix string.
51-
const PREFIX: &'static str;
52-
53-
/// Converts an integer to corresponding radix digit.
54-
fn digit(x: u8) -> u8;
55-
56-
/// Format an unsigned integer using the radix using a formatter.
57-
fn fmt_int<T: DisplayInt>(&self, mut x: T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58-
// The radix can be as low as 2, so we need a buffer of at least 128
59-
// characters for a base 2 number.
60-
let zero = T::zero();
61-
let mut buf = [MaybeUninit::<u8>::uninit(); 128];
62-
let mut curr = buf.len();
63-
let base = T::from_u8(Self::BASE);
64-
65-
// Accumulate each digit of the number from the least significant
66-
// to the most significant figure.
67-
loop {
68-
let n = x % base; // Get the current place value.
69-
x = x / base; // Deaccumulate the number.
70-
curr -= 1;
71-
buf[curr].write(Self::digit(n.to_u8())); // Store the digit in the buffer.
72-
if x == zero {
73-
// No more digits left to accumulate.
74-
break;
75-
};
76-
}
34+
// Formatting of integers with a non-decimal radix.
35+
macro_rules! radix_integer {
36+
(fmt::$Trait:ident for $Signed:ident and $Unsigned:ident, $prefix:expr, $dig_tab:expr) => {
37+
#[stable(feature = "rust1", since = "1.0.0")]
38+
impl fmt::$Trait for $Unsigned {
39+
/// Format unsigned integers in the radix.
40+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41+
// Check arguments at compile time.
42+
assert!($Unsigned::MIN == 0);
43+
$dig_tab.as_ascii().unwrap();
7744

78-
// SAFETY: `curr` is initialized to `buf.len()` and is only decremented, so it can't overflow. It is
79-
// decremented exactly once for each digit. Since u128 is the widest fixed width integer format supported,
80-
// the maximum number of digits (bits) is 128 for base-2, so `curr` won't underflow as well.
81-
let buf = unsafe { buf.get_unchecked(curr..) };
82-
// SAFETY: The only chars in `buf` are created by `Self::digit` which are assumed to be
83-
// valid UTF-8
84-
let buf = unsafe {
85-
str::from_utf8_unchecked(slice::from_raw_parts(
86-
MaybeUninit::slice_as_ptr(buf),
87-
buf.len(),
88-
))
89-
};
90-
f.pad_integral(true, Self::PREFIX, buf)
91-
}
92-
}
45+
// ASCII digits in ascending order are used as a lookup table.
46+
const DIG_TAB: &[u8] = $dig_tab;
47+
const BASE: $Unsigned = DIG_TAB.len() as $Unsigned;
48+
const MAX_DIG_N: usize = $Unsigned::MAX.ilog(BASE) as usize + 1;
49+
50+
// Buffer digits of self with right alignment.
51+
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DIG_N];
52+
// Count the number of bytes in buf that are not initialized.
53+
let mut offset = buf.len();
54+
55+
// Accumulate each digit of the number from the least
56+
// significant to the most significant figure.
57+
let mut remain = *self;
58+
loop {
59+
let digit = remain % BASE;
60+
remain /= BASE;
9361

94-
/// A binary (base 2) radix
95-
#[derive(Clone, PartialEq)]
96-
struct Binary;
97-
98-
/// An octal (base 8) radix
99-
#[derive(Clone, PartialEq)]
100-
struct Octal;
101-
102-
/// A hexadecimal (base 16) radix, formatted with lower-case characters
103-
#[derive(Clone, PartialEq)]
104-
struct LowerHex;
105-
106-
/// A hexadecimal (base 16) radix, formatted with upper-case characters
107-
#[derive(Clone, PartialEq)]
108-
struct UpperHex;
109-
110-
macro_rules! radix {
111-
($T:ident, $base:expr, $prefix:expr, $($x:pat => $conv:expr),+) => {
112-
unsafe impl GenericRadix for $T {
113-
const BASE: u8 = $base;
114-
const PREFIX: &'static str = $prefix;
115-
fn digit(x: u8) -> u8 {
116-
match x {
117-
$($x => $conv,)+
118-
x => panic!("number not in the range 0..={}: {}", Self::BASE - 1, x),
62+
// SAFETY: All of the decimals fit in buf due to MAX_DEC_N
63+
// and the break condition below ensures at least 1 more
64+
// decimal.
65+
unsafe { core::hint::assert_unchecked(offset >= 1) }
66+
// SAFETY: The offset counts down from its initial buf.len()
67+
// without underflow due to the previous precondition.
68+
unsafe { core::hint::assert_unchecked(offset <= buf.len()) }
69+
offset -= 1;
70+
buf[offset].write(DIG_TAB[digit as usize]);
71+
if remain == 0 {
72+
break;
73+
}
11974
}
75+
76+
// SAFETY: All buf content since offset is set.
77+
let written = unsafe { buf.get_unchecked(offset..) };
78+
// SAFETY: Writes are ASCII numbers exclusively.
79+
let as_str = unsafe {
80+
str::from_utf8_unchecked(slice::from_raw_parts(
81+
MaybeUninit::slice_as_ptr(written),
82+
written.len(),
83+
))
84+
};
85+
f.pad_integral(true, $prefix, as_str)
12086
}
12187
}
122-
}
123-
}
12488

125-
radix! { Binary, 2, "0b", x @ 0 ..= 1 => b'0' + x }
126-
radix! { Octal, 8, "0o", x @ 0 ..= 7 => b'0' + x }
127-
radix! { LowerHex, 16, "0x", x @ 0 ..= 9 => b'0' + x, x @ 10 ..= 15 => b'a' + (x - 10) }
128-
radix! { UpperHex, 16, "0x", x @ 0 ..= 9 => b'0' + x, x @ 10 ..= 15 => b'A' + (x - 10) }
129-
130-
macro_rules! int_base {
131-
(fmt::$Trait:ident for $T:ident -> $Radix:ident) => {
13289
#[stable(feature = "rust1", since = "1.0.0")]
133-
impl fmt::$Trait for $T {
90+
impl fmt::$Trait for $Signed {
91+
/// Format signed integers in the two’s-complement form.
13492
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135-
$Radix.fmt_int(*self, f)
93+
assert!($Signed::MIN < 0);
94+
fmt::$Trait::fmt(&(*self as $Unsigned), f)
13695
}
13796
}
13897
};
13998
}
14099

141-
macro_rules! integer {
142-
($Int:ident, $Uint:ident) => {
143-
int_base! { fmt::Binary for $Uint -> Binary }
144-
int_base! { fmt::Octal for $Uint -> Octal }
145-
int_base! { fmt::LowerHex for $Uint -> LowerHex }
146-
int_base! { fmt::UpperHex for $Uint -> UpperHex }
147-
148-
// Format signed integers as unsigned (two’s complement representation).
149-
#[stable(feature = "rust1", since = "1.0.0")]
150-
impl fmt::Binary for $Int {
151-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152-
fmt::Binary::fmt(&(*self as $Uint), f)
153-
}
154-
}
155-
#[stable(feature = "rust1", since = "1.0.0")]
156-
impl fmt::Octal for $Int {
157-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158-
fmt::Octal::fmt(&(*self as $Uint), f)
159-
}
160-
}
161-
#[stable(feature = "rust1", since = "1.0.0")]
162-
impl fmt::LowerHex for $Int {
163-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164-
fmt::LowerHex::fmt(&(*self as $Uint), f)
165-
}
166-
}
167-
#[stable(feature = "rust1", since = "1.0.0")]
168-
impl fmt::UpperHex for $Int {
169-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170-
fmt::UpperHex::fmt(&(*self as $Uint), f)
171-
}
172-
}
100+
// Formatting of integers with a non-decimal radix.
101+
macro_rules! radix_integers {
102+
($Signed:ident, $Unsigned:ident) => {
103+
radix_integer! { fmt::Binary for $Signed and $Unsigned, "0b", b"01" }
104+
radix_integer! { fmt::Octal for $Signed and $Unsigned, "0o", b"01234567" }
105+
radix_integer! { fmt::LowerHex for $Signed and $Unsigned, "0x", b"0123456789abcdef" }
106+
radix_integer! { fmt::UpperHex for $Signed and $Unsigned, "0x", b"0123456789ABCDEF" }
173107
};
174108
}
175-
integer! { isize, usize }
176-
integer! { i8, u8 }
177-
integer! { i16, u16 }
178-
integer! { i32, u32 }
179-
integer! { i64, u64 }
180-
integer! { i128, u128 }
109+
radix_integers! { isize, usize }
110+
radix_integers! { i8, u8 }
111+
radix_integers! { i16, u16 }
112+
radix_integers! { i32, u32 }
113+
radix_integers! { i64, u64 }
114+
radix_integers! { i128, u128 }
181115

182116
macro_rules! impl_Debug {
183117
($($T:ident)*) => {

0 commit comments

Comments
 (0)