Skip to content

Commit b72377b

Browse files
committed
perf: use bit shifts for power-of-two formatting
1 parent bc691c1 commit b72377b

2 files changed

Lines changed: 53 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Changed
1313

1414
- Specialize `Ord::cmp`, `const_eq`, `const_is_zero` for small sizes ([#561])
15+
- Use bit shifts for power-of-two formatting (binary, octal, hex) instead of division ([#565])
1516

1617
[#561]: https://github.com/recmo/uint/pull/561
18+
[#565]: https://github.com/recmo/uint/pull/565
1719

1820
## [1.17.2] - 2025-12-28
1921

src/fmt.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,50 @@ impl<const BITS: usize, const LIMBS: usize> fmt::Debug for Uint<BITS, LIMBS> {
8383
}
8484

8585
impl_fmt!(fmt::Display; base::Decimal, "");
86-
impl_fmt!(fmt::Binary; base::Binary, "b");
87-
impl_fmt!(fmt::Octal; base::Octal, "o");
88-
impl_fmt!(fmt::LowerHex; base::Hexadecimal, "x");
89-
impl_fmt!(fmt::UpperHex; base::Hexadecimal, "X");
86+
macro_rules! impl_fmt_pow2 {
87+
($tr:path; $base:ty, $bits_per_digit:literal, $upper:literal) => {
88+
impl<const BITS: usize, const LIMBS: usize> $tr for Uint<BITS, LIMBS> {
89+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90+
if let Ok(small) = u64::try_from(self) {
91+
return <u64 as $tr>::fmt(&small, f);
92+
}
93+
if let Ok(small) = u128::try_from(self) {
94+
return <u128 as $tr>::fmt(&small, f);
95+
}
96+
97+
let alphabet: &[u8; 16] = if $upper {
98+
b"0123456789ABCDEF"
99+
} else {
100+
b"0123456789abcdef"
101+
};
102+
let mask: u64 = (1 << $bits_per_digit) - 1;
103+
104+
let bit_len = self.bit_len();
105+
let total_digits = bit_len.div_ceil($bits_per_digit);
106+
107+
let mut s = StackString::<BITS>::new();
108+
let mut i = total_digits;
109+
while i > 0 {
110+
i -= 1;
111+
let bit_offset = i * $bits_per_digit;
112+
let limb_idx = bit_offset / 64;
113+
let bit_idx = bit_offset % 64;
114+
let mut digit = (self.limbs[limb_idx] >> bit_idx) & mask;
115+
if bit_idx + $bits_per_digit > 64 && limb_idx + 1 < LIMBS {
116+
digit |= (self.limbs[limb_idx + 1] << (64 - bit_idx)) & mask;
117+
}
118+
s.push_byte(alphabet[digit as usize]);
119+
}
120+
f.pad_integral(true, <$base>::PREFIX, s.as_str())
121+
}
122+
}
123+
};
124+
}
125+
126+
impl_fmt_pow2!(fmt::Binary; base::Binary, 1, false);
127+
impl_fmt_pow2!(fmt::Octal; base::Octal, 3, false);
128+
impl_fmt_pow2!(fmt::LowerHex; base::Hexadecimal, 4, false);
129+
impl_fmt_pow2!(fmt::UpperHex; base::Hexadecimal, 4, true);
90130

91131
/// A stack-allocated buffer that implements [`fmt::Write`].
92132
pub(crate) struct StackString<const SIZE: usize> {
@@ -115,6 +155,13 @@ impl<const SIZE: usize> StackString<SIZE> {
115155
const fn as_bytes(&self) -> &[u8] {
116156
unsafe { core::slice::from_raw_parts(self.buf.as_ptr().cast(), self.len) }
117157
}
158+
159+
#[inline]
160+
fn push_byte(&mut self, b: u8) {
161+
debug_assert!(self.len < SIZE);
162+
unsafe { self.buf.as_mut_ptr().add(self.len).cast::<u8>().write(b) };
163+
self.len += 1;
164+
}
118165
}
119166

120167
impl<const SIZE: usize> fmt::Write for StackString<SIZE> {

0 commit comments

Comments
 (0)